#include <string> #include <fstream> #include <istream> #include <sstream> #include <iostream> #include <assert.h> #include "ogImage.h" #include "objgfx.h" bool Win3xBitmapHeader::Deserialize(std::istream& stream) { if (!stream) return false; // Read in each field, and check for any failure. Return false on failure if (!stream.read(reinterpret_cast<char *>(&ImageFileType), sizeof(ImageFileType))) return false; if (!stream.read(reinterpret_cast<char *>(&FileSize), sizeof(FileSize))) return false; if (!stream.read(reinterpret_cast<char *>(&Reserved1), sizeof(Reserved1))) return false; if (!stream.read(reinterpret_cast<char *>(&Reserved2), sizeof(Reserved2))) return false; if (!stream.read(reinterpret_cast<char *>(&ImageDataOffset), sizeof(ImageDataOffset))) return false; return true; } // bool Win3xBitmapHeader::Deserialize() std::string Win3xBitmapHeader::ToString() { return "ImageFileType = " + std::to_string(ImageFileType) + "\r\n" + "FileSize = " + std::to_string(FileSize) + "\r\n" + "Reserved1 = " + std::to_string(Reserved1) + "\r\n" + "Reserved2 = " + std::to_string(Reserved2) + "\r\n" + "ImageDataOffset = " + std::to_string(ImageDataOffset) + "\r\n"; } // string Win3xBitmapHeader::ToString() bool Win3xBitmapHeader::IsMatch() { return (ImageFileType == 0x4D42 && FileSize != 0 && Reserved1 == 0 && Reserved2 == 0 && ImageDataOffset != 0); } // bool Win3xBitmapHeader::IsMatch() bool Win3xBitmapHeader::Serialize(std::ostream& stream) { if (!stream) return false; // Read in each field, and check for any failure. Return false on failure if (!stream.write(reinterpret_cast<char *>(&ImageFileType), sizeof(ImageFileType))) return false; if (!stream.write(reinterpret_cast<char *>(&FileSize), sizeof(FileSize))) return false; if (!stream.write(reinterpret_cast<char *>(&Reserved1), sizeof(Reserved1))) return false; if (!stream.write(reinterpret_cast<char *>(&Reserved2), sizeof(Reserved2))) return false; if (!stream.write(reinterpret_cast<char *>(&ImageDataOffset), sizeof(ImageDataOffset))) return false; return true; } // bool Win3xBitmapHeader::Serialize() size_t Win3xBitmapHeader::Size() { return sizeof(ImageFileType) + sizeof(FileSize) + sizeof(Reserved1) + sizeof(Reserved2) + sizeof(ImageDataOffset); } // size_t Win3xBitmapHeader::Size() bool Win3xBitmapInfoHeader::Deserialize(std::istream& stream) { if (!stream) return false; // Read in each field, and check for any failure. Return false on failure if (!stream.read(reinterpret_cast<char *>(&HeaderSize), sizeof(HeaderSize))) return false; if (!stream.read(reinterpret_cast<char *>(&ImageWidth), sizeof(ImageWidth))) return false; if (!stream.read(reinterpret_cast<char *>(&ImageHeight), sizeof(ImageHeight))) return false; if (!stream.read(reinterpret_cast<char *>(&NumberOfImagePlanes), sizeof(NumberOfImagePlanes))) return false; if (!stream.read(reinterpret_cast<char *>(&BitsPerPixel), sizeof(BitsPerPixel))) return false; if (!stream.read(reinterpret_cast<char *>(&CompressionMethod), sizeof(CompressionMethod))) return false; if (!stream.read(reinterpret_cast<char *>(&SizeOfBitmap), sizeof(SizeOfBitmap))) return false; if (!stream.read(reinterpret_cast<char *>(&HorzResolution), sizeof(HorzResolution))) return false; if (!stream.read(reinterpret_cast<char *>(&VertResolution), sizeof(VertResolution))) return false; if (!stream.read(reinterpret_cast<char *>(&NumColoursUsed), sizeof(NumColoursUsed))) return false; if (!stream.read(reinterpret_cast<char *>(&NumSignificantColours), sizeof(NumSignificantColours))) return false; return true; } // bool Win3xBitmapInfoHeader::Deserialize() std::string Win3xBitmapInfoHeader::ToString() { return "HeaderSize = " + std::to_string(HeaderSize) + "\r\n" + "ImageWidth = " + std::to_string(ImageWidth) + "\r\n" + "ImageHeight = " + std::to_string(ImageHeight) + "\r\n" + "NumberOfImagePlanes = " + std::to_string(NumberOfImagePlanes) + "\r\n" + "BitsPerPixel = "+ std::to_string(BitsPerPixel) + "\r\n" + "CompressionMethod = " + std::to_string(CompressionMethod) + "\r\n" + "SizeOfBitmap = " + std::to_string(SizeOfBitmap) + "\r\n" + "HorzResolution = " + std::to_string(HorzResolution) + "\r\n" + "VertResolution = " + std::to_string(VertResolution) + "\r\n" + "NumColoursUsed = " + std::to_string(NumColoursUsed) + "\r\n" + "NumSignificantColours = " + std::to_string(NumSignificantColours) + "\r\n"; } // string Win3xBitmapInfoHeader::ToString() bool Win3xBitmapInfoHeader::IsMatch() { return (HeaderSize != 0 && ImageWidth != 0 && ImageHeight != 0 && CompressionMethod == 0 && (BitsPerPixel == 8 || BitsPerPixel == 24)); } bool Win3xBitmapInfoHeader::Serialize(std::ostream& stream) { if (!stream) return false; // Read in each field, and return false on failure if (!stream.write(reinterpret_cast<char *>(&HeaderSize), sizeof(HeaderSize))) return false; if (!stream.write(reinterpret_cast<char *>(&ImageWidth), sizeof(ImageWidth))) return false; if (!stream.write(reinterpret_cast<char *>(&ImageHeight), sizeof(ImageHeight))) return false; if (!stream.write(reinterpret_cast<char *>(&NumberOfImagePlanes), sizeof(NumberOfImagePlanes))) return false; if (!stream.write(reinterpret_cast<char *>(&BitsPerPixel), sizeof(BitsPerPixel))) return false; if (!stream.write(reinterpret_cast<char *>(&CompressionMethod), sizeof(CompressionMethod))) return false; if (!stream.write(reinterpret_cast<char *>(&SizeOfBitmap), sizeof(SizeOfBitmap))) return false; if (!stream.write(reinterpret_cast<char *>(&HorzResolution), sizeof(HorzResolution))) return false; if (!stream.write(reinterpret_cast<char *>(&VertResolution), sizeof(VertResolution))) return false; if (!stream.write(reinterpret_cast<char *>(&NumColoursUsed), sizeof(NumColoursUsed))) return false; if (!stream.write(reinterpret_cast<char *>(&NumSignificantColours), sizeof(NumSignificantColours))) return false; return true; } // bool Win3xBitmapInfoHeader::Serialize() size_t Win3xBitmapInfoHeader::Size() { return sizeof(HeaderSize) + sizeof(ImageWidth) + sizeof(ImageHeight) + sizeof(NumberOfImagePlanes) + sizeof(BitsPerPixel) + sizeof(CompressionMethod) + sizeof(SizeOfBitmap) + sizeof(HorzResolution) + sizeof(VertResolution) + sizeof(NumColoursUsed) + sizeof(NumSignificantColours); } // size_t Win3xBitmapInfoHeader::Size() bool RGBQuad::Deserialize(std::istream& stream) { if (!stream) return false; // Read in each field, and return false on failure if (!stream.read(reinterpret_cast<char *>(&rgbBlue), sizeof(rgbBlue))) return false; if (!stream.read(reinterpret_cast<char *>(&rgbGreen), sizeof(rgbGreen))) return false; if (!stream.read(reinterpret_cast<char *>(&rgbRed), sizeof(rgbRed))) return false; if (!stream.read(reinterpret_cast<char *>(&rgbReserved), sizeof(rgbReserved))) return false; return true; } // bool RGBQuad::Deserialize() std::string RGBQuad::ToString() { return "rgbRed = " + to_string(rgbRed) + "\r\n" + "rgbGreen = " + to_string(rgbGreen) + "\r\n" + "rgbBlue = " + to_string(rgbBlue) + "\r\n" + "rgbReserved = " + to_string(rgbReserved) + "\r\n"; } // string RGBQuad::ToString() bool RGBQuad::Serialize(std::ostream& stream) { if (!stream) return false; // Read in each field, and return false on failure if (!stream.write(reinterpret_cast<char *>(&rgbBlue), sizeof(rgbBlue))) return false; if (!stream.write(reinterpret_cast<char *>(&rgbGreen), sizeof(rgbGreen))) return false; if (!stream.write(reinterpret_cast<char *>(&rgbRed), sizeof(rgbRed))) return false; if (!stream.write(reinterpret_cast<char *>(&rgbReserved), sizeof(rgbReserved))) return false; return true; } // bool RGBQuad::Serialize() size_t RGBQuad::Size() { return sizeof(rgbBlue) + sizeof(rgbGreen) + sizeof(rgbRed) + sizeof(rgbReserved); } // size_t RGBQuad::Size() bool IsBMP(std::istream& stream) { // Check for BMP Win3xBitmapHeader bmpHeader; bmpHeader.Deserialize(stream); Win3xBitmapInfoHeader bmpInfoHeader; bmpInfoHeader.Deserialize(stream); if (bmpHeader.IsMatch() && bmpInfoHeader.IsMatch()) { std::cout << bmpHeader.ToString(); std::cout << bmpInfoHeader.ToString(); return true; } return false; } // bool IsBMP() /********************************************** * ogImage **********************************************/ static const std::map<ogImageType, std::function<bool(std::istream&)>>& CreateIsImageMap() { static std::map<ogImageType, std::function<bool(std::istream&)>> IsImage; //IsImage[NoImage] = ([&](std::istream&) -> bool { return false; }); IsImage[BMP] = &IsBMP; return IsImage; } // map<> CreateIsImageMap() std::map<ogImageType, std::function<bool(std::istream&)>> ogImage::IsImage = CreateIsImageMap(); ogImage::ogImage() : surface(nullptr), options(nullptr), output(nullptr), input(nullptr) { Decode[NoImage] = &ogImage::NoOp; Decode[BMP] = &ogImage::DecodeBMP; Encode[NoImage] = &ogImage::NoOp; Encode[BMP] = &ogImage::EncodeBMP; } // ogImage::ogImage() bool ogImage::DecodeBMP() { // ogImage::ImageType() has determined we're a BMP, so we only need to do // minimal sanity checks on the header information. Win3xBitmapHeader bmpHeader; Win3xBitmapInfoHeader bmpInfoHeader; if (!bmpHeader.Deserialize(*input)) { std::cout << "!bmpHeader" << std::endl; return false; } if (!bmpInfoHeader.Deserialize(*input)) { std::cout << "!bmpInfoHeader" << std::endl; return false; } size_t lineSize; size_t paddington; char linePadding[4]; std::cout <<"DecodeBMP" << std::endl; if (bmpInfoHeader.BitsPerPixel == 8) { if (!surface->ogCreate(bmpInfoHeader.ImageWidth, bmpInfoHeader.ImageHeight, OG_PIXFMT_8BPP)) return false; lineSize = ((bmpInfoHeader.ImageWidth+3) >> 2) << 2; // round up to the nearest 4 bytes paddington = lineSize - bmpInfoHeader.ImageWidth; // see if we have a remainder if (paddington > 4) return false; // this would be odd RGBQuad quad; for (unsigned colour = 0; colour < 256; colour++) { if (!quad.Deserialize(*input)) return false; surface->ogSetPalette(colour, quad.rgbRed, quad.rgbGreen, quad.rgbBlue, quad.rgbReserved); } // for colour for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0 { char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y)); if (ptr == nullptr) return false; // this doesn't have to be a complete failure, fix later if (!input->read(ptr, bmpInfoHeader.ImageWidth)) return false; if (paddington != 0) { if (!input->read(linePadding, paddington)) return false; // double check this } } // for y } else if (bmpInfoHeader.BitsPerPixel == 24) { ogPixelFmt BMPPixelFmt(24, 16,8,0,0, 8,8,8,0); if (!surface->ogCreate(bmpInfoHeader.ImageWidth, bmpInfoHeader.ImageHeight, OG_PIXFMT_24BPP)) return false; size_t widthInBytes = bmpInfoHeader.ImageWidth*3; // 3 represents how many bytes per pixel lineSize = ((widthInBytes+3) >> 2) << 2; // round up to the nearest 4 bytes paddington = lineSize - widthInBytes; // see if we have a remainder if (paddington > 4) return false; for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) { char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y)); if (ptr == nullptr) return false; // this doesn't have to be a complete failure, fix later if (!input->read(ptr, widthInBytes)) return false; if (paddington != 0) { if (!input->read(linePadding, paddington)) return false; } } // for y } return true; } // bool ogImage::DecodeBMP() bool ogImage::EncodeBMP() { Win3xBitmapHeader bmpHeader; Win3xBitmapInfoHeader bmpInfoHeader; size_t paddington; size_t lineSize; const char linePadding[4] = {0,0,0,0}; ogRGBA8 ogPalette[256]; // used to get the palette from the surface bmpHeader.ImageFileType = 0x4D42; bmpHeader.FileSize = 0; // fill in later //bmpHeader.Reserved1 = 0; // set by constructor //bmpHeader.Reserved2 = 0; // set by constructor bmpHeader.ImageDataOffset = bmpHeader.Size() + bmpInfoHeader.Size(); bmpInfoHeader.HeaderSize = bmpInfoHeader.Size(); bmpInfoHeader.ImageWidth = surface->ogGetMaxX()+1; bmpInfoHeader.ImageHeight = surface->ogGetMaxY()+1; bmpInfoHeader.NumberOfImagePlanes = 1; // bmpInfoHeader.BitsPerPixel is set below bmpInfoHeader.CompressionMethod = 0; bmpInfoHeader.SizeOfBitmap = 0; bmpInfoHeader.HorzResolution = 0; // option bmpInfoHeader.VertResolution = 0; // option switch (surface->ogGetBPP()) { case 8: bmpInfoHeader.BitsPerPixel = 8; // 8bpp, 256 colours bmpInfoHeader.NumColoursUsed = 1 << surface->ogGetBPP(); bmpInfoHeader.NumSignificantColours = 0; // option // This is hard to calculate dynamically. Sod it. bmpHeader.ImageDataOffset += 1024;// adjust by palette size lineSize = ((bmpInfoHeader.ImageWidth+3) >> 2) << 2; // multiple of 4 bmpInfoHeader.SizeOfBitmap = lineSize*bmpInfoHeader.ImageHeight; bmpHeader.FileSize = bmpHeader.ImageDataOffset+bmpInfoHeader.SizeOfBitmap; // Write the headers if (!bmpHeader.Serialize(*output)) return false; if (!bmpInfoHeader.Serialize(*output)) return false; // all rows are aligned to the nearest 4 bytes. Figure out if we need to pad. paddington = lineSize-bmpInfoHeader.ImageWidth; surface->ogGetPalette(ogPalette); for (size_t index = 0; index < sizeof(ogPalette) / sizeof(ogPalette[0]); index++) { RGBQuad quad(ogPalette[index]); if (!quad.Serialize(*output)) return false; } // for index for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0 { char * ptr = reinterpret_cast<char *>(surface->ogGetPtr(0, y)); if (ptr != nullptr) { if (!output->write(ptr, bmpInfoHeader.ImageWidth)) return false; } // Is there any padding to add to the end of the line? if (paddington != 0) { if (!output->write(linePadding, paddington)) return false; } } // for y break; case 32: case 24: case 16: case 15: // 15, 16, and 32 bpp are all treated as 24bpp bmpInfoHeader.BitsPerPixel = 24; bmpInfoHeader.NumColoursUsed = 0; bmpInfoHeader.NumSignificantColours = 0; lineSize = ((bmpInfoHeader.ImageWidth*3+3) >> 2) << 2; bmpInfoHeader.SizeOfBitmap = lineSize*bmpInfoHeader.ImageHeight; bmpHeader.FileSize = bmpHeader.ImageDataOffset+bmpInfoHeader.SizeOfBitmap; // Write the headers if (!bmpHeader.Serialize(*output)) return false; if (!bmpInfoHeader.Serialize(*output)) return false; // all rows are aligned to the nearest 4 bytes. Figure out if we need to pad. paddington = lineSize-bmpInfoHeader.ImageWidth*3; // 3 represents how many bytes per pixel uInt8 red, green, blue; for (unsigned y = surface->ogGetMaxY()+1; y --> 0 ;) // y goes to 0 { for (unsigned x = 0; x <= surface->ogGetMaxX(); x++) { // Unpack the pixel and write it out. surface->ogUnpack(surface->ogGetPixel(x, y), red, green, blue); if (!output->write(reinterpret_cast<char *>(&blue), sizeof(blue))) return false; if (!output->write(reinterpret_cast<char *>(&green), sizeof(green))) return false; if (!output->write(reinterpret_cast<char *>(&red), sizeof(red))) return false; } // for x // Is there any padding to add to the end of the line? if (paddington != 0) { if (!output->write(linePadding, paddington)) return false; } } // for y break; default: return false; // we can't encode anything else (I'm not sure there *is* anything else) } // switch return true; } // bool ogImage::EncodeBMP() ogImageType ogImage::ImageType(std::istream& input) { ogImageType imageType = NoImage; // space for the header char header[128]; // This really should be as large as the largest header we know of for (size_t index = 0; index < std::extent<decltype(header)>::value; index++) { header[index] = 0; // clear the header } // for index size_t size = sizeof(header); if (!input.read(header, size)) return NoImage; // This isn't necessarily true. Might just be a small image. // Figure out how many bytes we read in. streamsize bytesRead = input.gcount(); if (bytesRead != 0) { // Try to determine what it really is for (auto iType : IsImage) { mstream mb(header, bytesRead); // This creates a new istream from a memory stream, // which is wrapped adound the header. //istream stream(&mstream(header, bytesRead)); istream stream(&mb); if (iType.second(stream)) // Did we find it? { imageType = iType.first; // Found it break; } // if } // for iType std::cout << "bRead: " << bytesRead << std::endl; // Now seek backwards in the stream. input.seekg(-bytesRead, std::ios_base::cur); } return imageType; } // ogImageType ogImage::ImageType() ogImageType ogImage::ImageType(const std::string& filename) { std::ifstream input(filename, ios::in | std::ios_base::binary); ogImageType imageType = NoImage; // Sanity check if (input) { // Use the stream version imageType = ogImage::ImageType(input); // close the file input.close(); } // if return imageType; } // ogImageType ogImage::ImageType() bool ogImage::Load(const std::string& filename, ogSurface & surface) { std::cout << "Loading " << filename << std::endl; bool success = false; // assume failure ifstream stream(filename, ios_base::in | ios_base::binary); std::cout << "Loaded" << std::endl; if (stream) { success = Load(stream, surface); stream.close(); } // if stream return success; } // ogSurface * ogImage::Load() bool ogImage::Load(std::istream& stream, ogSurface& surface) { // First we need to find out what type of graphics file it is: std::cout << "eIT" << std::endl; ogImageType imageType = ogImage::ImageType(stream); std::cout << "rIT" << std::endl; if (imageType == NoImage) return false; this->surface = &surface; this->input = &stream; this->options = nullptr; // shouldn't be necessary this->output = nullptr; // shouldn't be necessary assert(Decode.count(imageType) == 1); // make sure we can handle it std::cout << "Load-istream2" << std::endl; return (this->*Decode[imageType])(); // Decode } // bool ogImage::Load() bool ogImage::Save(const std::string& filename, ogSurface &surface, ogImageType imageType, ogImageOptions * options) { bool success = false; // assume failure ofstream stream(filename, ios_base::out | ios_base::binary); // try to open the file if (stream) { // Use the stream version success = Save(stream, surface, imageType, options); // close the file stream.close(); } // if return success; } // bool ogImage::Save() bool ogImage::Save(std::ostream& stream, ogSurface &surface, ogImageType imageType, ogImageOptions * options) { bool success = false; // assume failure if (stream && surface.ogAvail()) { // Since surface is a reference, it cannot [normally] be null, // so no checking needs to be done in any of the encoder functions. this->surface = &surface; this->options = options; this->output = &stream; assert(Encode.count(imageType) == 1); // Sanity check // Call the specific encoder success = (this->*Encode[imageType])(); } // if return success; } // bool ogImage::Save() #if 0 class membuf : public basic_streambuf<char> { public: membuf(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } } Usage: char *mybuffer; size_t length; // ... allocate "mybuffer", put data into it, set "length" membuf mb(mybuffer, length); istream reader(&mb); // use "reader" #endif