/********************************************************************** Filename : GImage.cpp Content : Memory buffer Image class loading and manipulation Created : Authors : Copyright : (c) 2001-2006 Scaleform Corp. All Rights Reserved. Notes : Licensees may use this file in accordance with the valid Scaleform Commercial License Agreement provided with the software. This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE. **********************************************************************/ #include "GImage.h" #include "GJPEGUtil.h" #include "GSysFile.h" #include "GStd.h" #include "GHeapNew.h" struct GImageColorMap { UInt StartIndex; UInt NumEntries; bool HasAlpha; struct Entry { UInt32 R: 8; UInt32 G: 8; UInt32 B: 8; UInt32 A: 8; } Entries[256]; }; // ***** GImageBase // Set pixel, sets only the appropriate channels void GImageBase::SetPixelRGBA(SInt x, SInt y, UInt32 color) { // Bounds check if ( (((UInt)x) >= Width) || (((UInt)y) >= Height)) return; if (Format >= Image_DXT1) { GASSERT(0); return; } UByte *pline = GetScanline(y); switch(Format) { case Image_ARGB_8888: *(((UInt32*)pline) + x) = GByteUtil::LEToSystem(color); break; case Image_RGB_888: // Data order is packed 24-bit, RGBRGB..., regardless of the endian-ness of the CPU. *(pline + x * 3) = (UByte) color & 0xFF; *(pline + x * 3 + 1) = (UByte) (color>>8) & 0xFF; *(pline + x * 3 + 2) = (UByte) (color>>16) & 0xFF; break; case Image_A_8: *(pline + x) = (UByte) (color >> 24); break; case Image_L_8: *(pline + x) = (UByte) color & 0xFF; break; default: break; } } void GImageBase::SetPixelAlpha(SInt x, SInt y, UByte alpha) { if ( (((UInt)x) >= Width) || (((UInt)y) >= Height)) return; if (Format >= Image_DXT1) { GASSERT(0); return; } UByte *pline = GetScanline(y); switch(Format) { case Image_ARGB_8888: // Target is always little-endian *(pline + x * 4 + 3) = alpha; break; case Image_A_8: *(pline + x) = alpha; break; default: break; } } void GImageBase::SetPixelLum(SInt x, SInt y, UByte lum) { if ( (((UInt)x) >= Width) || (((UInt)y) >= Height)) return; if (Format >= Image_DXT1) { GASSERT(0); return; } UByte *pline = GetScanline(y); switch(Format) { case Image_ARGB_8888: *(pline + x * 4) = lum; *(pline + x * 4 + 1) = lum; *(pline + x * 4 + 2) = lum; break; case Image_RGB_888: *(pline + x * 3) = lum; *(pline + x * 3 + 1) = lum; *(pline + x * 3 + 2) = lum; break; case Image_L_8: *(pline + x) = lum; break; default: break; } } UInt GImageBase::GetBytesPerPixel(GImageBase::ImageFormat fmt) { switch(fmt) { case Image_A_8: case Image_L_8: case Image_P_8: return 1; case Image_ARGB_8888: return 4; case Image_RGB_888: return 3; case Image_DXT1: return 2; case Image_DXT3: case Image_DXT5: return 1; default: break; } return 0; } UInt GImageBase::GetPitch(ImageFormat fmt, UInt width) { switch(fmt) { case Image_A_8: case Image_L_8: case Image_P_8: return width; case Image_ARGB_8888: return width * 4; case Image_RGB_888: return (width * 3 + 3) & ~3; default: break; } return 0; } // Computes a hash of the given data buffer. // Hash function suggested by http://www.cs.yorku.ca/~oz/hash.html // Due to Dan Bernstein. Allegedly very good on strings. // // One problem with this hash function is that e.g. if you take a // bunch of 32-bit ints and hash them, their hash values will be // concentrated toward zero, instead of randomly distributed in // [0,2^32-1], because of shifting up only 5 bits per byte. GINLINE UPInt GImageBase_BernsteinHash(const void* pdataIn, UPInt size, UPInt seed = 5381) { const UByte* pdata = (const UByte*) pdataIn; UPInt h = seed; while (size > 0) { size--; h = ((h << 5) + h) ^ (UInt) pdata[size]; } return h; } // Compute a hash code based on image contents. Can be useful // for comparing images. Will return 0 if pData is null. UPInt GImageBase::ComputeHash() const { if (!pData || DataSize == 0) return 0; UPInt h = GImageBase_BernsteinHash(&Width, sizeof(Width)); h = GImageBase_BernsteinHash(&Height, sizeof(Height), h); h = GImageBase_BernsteinHash(&MipMapCount, sizeof(MipMapCount), h); h = GImageBase_BernsteinHash(pData, DataSize, h); return h; } UInt GImageBase::GetMipMapLevelSize(ImageFormat format, UInt w, UInt h) { UInt levelSize; if (format == Image_DXT1) levelSize = G_Max(1u, (w+3) / 4) * G_Max(1u, (h+3) / 4) * 8; else if (format >= Image_DXT3 && format <= Image_DXT5) levelSize = G_Max(1u, (w+3) / 4) * G_Max(1u, (h+3) / 4) * 16; else levelSize = GetPitch(format, w) * h; return levelSize; } UByte* GImageBase::GetMipMapLevelData(UInt level, UInt* pwidth, UInt* pheight, UInt* ppitch) { if (level > MipMapCount) return 0; if (level == 0) { if (pwidth) *pwidth = UInt(Width); if (pheight) *pheight = UInt(Height); if (ppitch) *ppitch = UInt(Pitch); return pData; } UInt32 w = Width; UInt32 h = Height; UByte* plevelData = pData; UInt32 pitch = Pitch; for(UInt i = 0; i < level; ++i) { plevelData += GetMipMapLevelSize(Format, w, h); w = G_Max(UInt32(1), w/2); h = G_Max(UInt32(1), h/2); pitch/= 2; } if (pwidth) *pwidth = UInt(w); if (pheight) *pheight = UInt(h); if (ppitch) *ppitch = pitch; //UInt(GetPitch(Format, w)); GASSERT(plevelData < (pData + DataSize)); if (plevelData < (pData + DataSize)) return plevelData; return NULL; } // ***** GImage implementation void GImage::CreateImageCopy(const GImageBase &src ) { // Copy data if (src.pData && (pData = (UByte*)GALLOC(src.DataSize, GStat_Image_Mem)) != 0) { DataSize= src.DataSize; Format = src.Format; Width = src.Width; Height = src.Height; Pitch = src.Pitch; MipMapCount = src.MipMapCount; ColorMap = src.ColorMap; memcpy(pData, src.pData, src.DataSize); } else { Format = Image_None; Width = Height = Pitch = 0; pData = 0; DataSize= 0; MipMapCount = 1; ColorMap.Clear(); } } GImage::GImage() { Format = Image_None; Width = Height = Pitch = 0; pData = 0; DataSize= 0; MipMapCount = 1; } GImage::GImage(ImageFormat format, UInt32 width, UInt32 height) { GFC_DEBUG_WARNING((width <= 0) || (height <=0), "GImage::GImage - creating image with zero size"); Pitch = GetPitch(format, width); // This size calculation accommodates DXT formats as well. DataSize = GetMipMapLevelSize(format, width, height); if ((pData = (UByte*)GHEAP_AUTO_ALLOC(this, DataSize)) != 0) // Was: GStat_Image_Mem { Format = format; Width = width; Height = height; memset(pData, 0, DataSize); } else { ClearImageBase(); } MipMapCount = 1; } GImage::~GImage() { if (pData) GFREE(pData); } void GImage::Clear() { if (pData) GFREE(pData); ClearImageBase(); } // Create an image (return 0 if allocation failed) GImage* GImage::CreateImage(ImageFormat format, UInt32 width, UInt32 height, GMemoryHeap* pimageHeap) { GMemoryHeap* pheap = pimageHeap ? pimageHeap : GMemory::GetGlobalHeap(); GImage* pimage = GHEAP_NEW(pheap) GImage(format, width, height); if (pimage->pData && (width !=0) && (height != 0)) return pimage; pimage->Release(); return 0; } // Raw comparison of data bool GImage::operator == (const GImage &src) const { if ((Format != src.Format) || (Width != src.Width) || (Height != src.Height) || (Pitch != src.Pitch) || (MipMapCount != src.MipMapCount) || (DataSize != src.DataSize) || (ColorMap.GetSize() != src.ColorMap.GetSize())) return 0; if (!pData) return (bool)(src.pData == 0); if (ColorMap.GetSize() && memcmp(&ColorMap[0], &src.ColorMap[0], ColorMap.GetSize() * sizeof(GColor))) return 0; // Return 1 if two buffers are identical (i.e. memcmp == 0) return memcmp(pData, src.pData, DataSize) == 0; } // ***** Image I/O utility functions // Write the given image to the given out stream, in jpeg format. bool GImage::WriteJpeg(GFile* pout, int quality, GJPEGSystem *psystem) { GJPEGOutput* pjout = psystem->CreateOutput(pout, Width, Height, quality); for (UInt y = 0; y < Height; y++) pjout->WriteScanline(GetScanline(y)); delete pjout; return 1; // Error code } static bool ConvertScanline(const UByte* psrcScanline, UInt srcBitsPerPixel, GImage::ImageFormat srcFormat, UInt srcScanlineSize, UByte* pdstScanline, UInt dstBitsPerPixel, GImage::ImageFormat dstFormat, UInt dstScanlineSize, void* pextraInfo) { GUNUSED(pextraInfo); UInt srcDelta = srcBitsPerPixel / 8; UInt dstDelta = dstBitsPerPixel / 8; if (srcFormat == GImage::Image_ARGB_8888) { if (dstFormat == GImage::Image_A_8) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { pdstScanline[j] = psrcScanline[i+3]; // take only alpha } return true; } else if (dstFormat == GImage::Image_RGB_888) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { pdstScanline[j] = psrcScanline[i]; pdstScanline[j+1] = psrcScanline[i+1]; pdstScanline[j+2] = psrcScanline[i+2]; } return true; } } else if (srcFormat == GImage::Image_RGB_888) { if (dstFormat == GImage::Image_A_8) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { UByte alpha = UByte((UInt(psrcScanline[i]) + psrcScanline[i+1] + psrcScanline[i+2])/3); pdstScanline[j] = alpha; } return true; } else if (dstFormat == GImage::Image_ARGB_8888) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { pdstScanline[j] = psrcScanline[i]; pdstScanline[j+1] = psrcScanline[i+1]; pdstScanline[j+2] = psrcScanline[i+2]; pdstScanline[j+3] = 0xFF; } return true; } } else if (srcFormat == GImage::Image_P_8) { // pextraInfo is GImageColorMap const GImageColorMap* pcolorMap = reinterpret_cast(pextraInfo); if (dstFormat == GImage::Image_A_8) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { const GImageColorMap::Entry& cmEntry = pcolorMap->Entries[psrcScanline[i]]; UByte alpha; if (pcolorMap->HasAlpha) alpha = cmEntry.A; else alpha = UByte((UInt(cmEntry.R) + cmEntry.G + cmEntry.B)/3); pdstScanline[j] = alpha; } return true; } else if (dstFormat == GImage::Image_RGB_888 || dstFormat == GImage::Image_ARGB_8888) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { const GImageColorMap::Entry& cmEntry = pcolorMap->Entries[psrcScanline[i]]; pdstScanline[j] = cmEntry.R; pdstScanline[j+1] = cmEntry.G; pdstScanline[j+2] = cmEntry.B; if (dstFormat == GImage::Image_ARGB_8888) pdstScanline[j+3] = cmEntry.A; } return true; } } else if (srcFormat == GImage::Image_A_8) { if (dstFormat == GImage::Image_RGB_888) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { pdstScanline[j] = psrcScanline[i]; pdstScanline[j+1] = psrcScanline[i]; pdstScanline[j+2] = psrcScanline[i]; } return true; } else if (dstFormat == GImage::Image_ARGB_8888) { for(UInt i = 0, j = 0; i < srcScanlineSize && j < dstScanlineSize; i += srcDelta, j += dstDelta) { pdstScanline[j] = 0xFF; pdstScanline[j+1] = 0xFF; pdstScanline[j+2] = 0xFF; pdstScanline[j+3] = psrcScanline[i]; } return true; } } return false; } GImage* GImage::ConvertImage(ImageFormat destFormat, GMemoryHeap *pimageHeap) { UInt w = Width; UInt h = Height; if (Format == destFormat) { AddRef(); return this; // no need conversion } GPtr pdstImage = *GImage::CreateImage(destFormat, w, h, pimageHeap); if (pdstImage) { for (UInt y = 0; y < h; y++) { const UByte* psrcScanline = GetScanline(y); UByte* pdstScanline = pdstImage->GetScanline(y); if (!ConvertScanline(psrcScanline, GetBytesPerPixel()*8, Format, GetPitch(), pdstScanline, pdstImage->GetBytesPerPixel()*8, destFormat, pdstImage->GetPitch(), NULL)) return 0; // unable to convert! } pdstImage->AddRef(); } return pdstImage; } // Write a 32-bit Targa format bitmap. Dead simple, no compression. bool GImage::WriteTga(GFile* pout) { if (!pout->IsWritable()) return false; pout->WriteUByte(0); // ID length // Color Map type // 0 - indicates that no color-map data is included with this image. // 1 - indicates that a color-map is included with this image. if (Format == Image_A_8) pout->WriteUByte(1); else pout->WriteUByte(0); // Image Type // 0 No Image Data Included // 1 Uncompressed, Color-mapped Image // 2 Uncompressed, True-color Image // 3 Uncompressed, Black-and-white Image // 9 Run-length encoded, Color-mapped Image // 10 Run-length encoded, True-color Image // 11 Run-length encoded, Black-and-white Image if (Format == Image_A_8) pout->WriteUByte(1); else pout->WriteUByte(2); // Color Map Specification if (Format == Image_A_8) { pout->WriteUInt16(0); // first entry index pout->WriteUInt16(256); // color map length (in entries) pout->WriteUByte(24); // color map entry size (in bits) } else { pout->WriteUInt16(0); pout->WriteUInt16(0); pout->WriteUByte(0); } pout->WriteUInt16(0); /* X origin */ pout->WriteUInt16(0); /* y origin */ pout->WriteUInt16((UInt16)Width); pout->WriteUInt16((UInt16)Height); if (Format == Image_A_8) pout->WriteUByte(8); // 8 bit bitmap else if (Format == Image_RGB_888) pout->WriteUByte(24); // 24 bit bitmap else pout->WriteUByte(32); // 32 bit bitmap UByte imageDescr = 0x20; if (Format == Image_ARGB_8888) imageDescr |= 8; pout->WriteUByte(imageDescr); // Image Descriptor: // Bits: 7 6 5 4 3 2 1 0 // |0 0| |r r| |a a a a| // where bits 3-0: These bits specify the number of attribute bits per // pixel. In the case of the TrueVista, these bits indicate // the number of bits per pixel which are designated as // Alpha Channel bits. // bits 5-4: These bits are used to indicate the order in which // pixel data is transferred from the file to the screen. // Bit 4 is for left-to-right ordering and bit 5 is for topto- // bottom ordering as shown below: // bits: 5 4 // 0 0 - bottom left // 0 1 - bottom right // 1 0 - top left // 1 1 - top right if (Format == Image_A_8) { // color map for (UInt i = 0; i < 256; ++i) { pout->WriteUByte(UByte(i)); pout->WriteUByte(UByte(i)); pout->WriteUByte(UByte(i)); } } for (UInt y = 0; y < Height; y++) { UByte* p = GetScanline(y); if (Format == Image_RGB_888) { // write 24-bit scanline for (UInt x = 0, wx = Width*3; x < wx; x += 3) { pout->WriteUByte(p[x + 2]); // B pout->WriteUByte(p[x + 1]); // G pout->WriteUByte(p[x + 0]); // R } } else if (Format == Image_ARGB_8888) { // write 32-bit scanline for (UInt x = 0, wx = Width*4; x < wx; x += 4) { pout->WriteUByte(p[x + 2]); // B pout->WriteUByte(p[x + 1]); // G pout->WriteUByte(p[x + 0]); // R pout->WriteUByte(p[x + 3]); // A } } else if (Format == Image_A_8) { // write indices for (UInt x = 0; x < Width; ++x) { pout->WriteUByte(p[x]); } } else { // unsupported format of scanline GASSERT(0); } } if (!pout->IsWritable()) return false; // error code ? return true; } // Create and read a new image from the stream. GImage* GImage::ReadTga(GFile* pin, ImageFormat destFormat, GMemoryHeap* pimageHeap) { if (!pin || !pin->IsValid()) return 0; UByte idLen = pin->ReadUByte(); UByte colorMapType = pin->ReadUByte(); UByte imageType = pin->ReadUByte(); // Color Map Specification GImageColorMap colorMap; colorMap.StartIndex = pin->ReadUInt16(); // first entry index colorMap.NumEntries = pin->ReadUInt16(); // color map length (in entries) UInt cmEntrySize = pin->ReadUByte(); // color map entry size (in bits) if (cmEntrySize > 0 && cmEntrySize != 24 && cmEntrySize != 32) return 0; // only 24- or 32-bits color maps are supported pin->ReadUInt16(); // X origin pin->ReadUInt16(); // Y origin UInt16 width = pin->ReadUInt16(); // width UInt16 height = pin->ReadUInt16(); // height UByte bitsPerPixel = pin->ReadUByte(); UByte imageDescr = pin->ReadUByte(); if (!((colorMapType == 0 && imageType == 2) || (colorMapType == 1 && imageType == 1))) { // only uncompressed RGB and color map formats are supported now return 0; } if (idLen > 0) pin->SkipBytes(idLen); ImageFormat format; SInt srcScanLineSize, dstScanLineSize; switch(bitsPerPixel) { case 8: format = Image_P_8; srcScanLineSize = width; break; case 24: format = Image_RGB_888; srcScanLineSize = width*3; break; case 32: format = Image_ARGB_8888; srcScanLineSize = width*4; break; default: return 0; // only 8/24/32-bits TrueType format supported } UByte destBitsPerPixel = bitsPerPixel; if (destFormat == Image_None) { #if (!defined(GFC_OS_PSP) && !defined(GFC_OS_PS2)) if (format == Image_P_8) { // by default, convert 256-colors TGA to RGB_888 if no alpha in palette if (cmEntrySize < 32) { destFormat = Image_RGB_888; dstScanLineSize = width*3; destBitsPerPixel = 24; } else { // or, to ARGB, if palette is 32 bit destFormat = Image_ARGB_8888; dstScanLineSize = width*4; destBitsPerPixel = 32; } } else #endif { destFormat = format; dstScanLineSize = srcScanLineSize; } } else { switch(destFormat) { case Image_A_8: destBitsPerPixel = 8; dstScanLineSize = width; break; case Image_P_8: destBitsPerPixel = 8; dstScanLineSize = width; break; case Image_RGB_888: destBitsPerPixel = 24; dstScanLineSize = width*3; break; case Image_ARGB_8888: destBitsPerPixel = 32; dstScanLineSize = width*4; break; default: // unsupported destination format return 0; } } if (colorMapType == 1 && imageType == 1) { // load color map UInt entrySizeInBytes = ((cmEntrySize+7)/8); if (entrySizeInBytes*colorMap.NumEntries > sizeof(colorMap.Entries)) return 0; // too big color map, only 256*4 is supported if (cmEntrySize == 32) colorMap.HasAlpha = true; else colorMap.HasAlpha = false; //Palette entries are BGR ordered for (UInt i = 0; i < colorMap.NumEntries; ++i) { #if (defined(GFC_OS_PSP) || defined(GFC_OS_PS2)) colorMap.Entries[i].R = pin->ReadUByte(); colorMap.Entries[i].G = pin->ReadUByte(); colorMap.Entries[i].B = pin->ReadUByte(); #else colorMap.Entries[i].B = pin->ReadUByte(); colorMap.Entries[i].G = pin->ReadUByte(); colorMap.Entries[i].R = pin->ReadUByte(); #endif if (cmEntrySize == 32) colorMap.Entries[i].A = pin->ReadUByte(); else colorMap.Entries[i].A = 0xFF; } } GPtr pimage = *CreateImage(destFormat, width, height, pimageHeap); if (pimage) { UByte* pscanline = 0; UByte scanlineBuf[4096 * 4]; if (UInt(srcScanLineSize) > sizeof(scanlineBuf)) pscanline = (UByte*)GALLOC(srcScanLineSize, GStat_Image_Mem); else pscanline = scanlineBuf; int ysl = (imageDescr & 0x20) ? 0 : height - 1; UInt y; for (y = 0; y < height; y++) { // read scan-line unsigned char* prgbData = pimage->GetScanline(G_Abs(ysl)); unsigned char* pcurScanline; if (format == destFormat) pcurScanline = prgbData; else pcurScanline = pscanline; if (pin->Read(pcurScanline, srcScanLineSize) != srcScanLineSize) break; // read error! if (format == Image_ARGB_8888) { // convert BGRA->RGBA for (UInt x = 0, wx = width*4; x < wx; x += 4) { UByte b = pcurScanline[x]; pcurScanline[x] = pcurScanline[x + 2]; pcurScanline[x + 2] = b; } } else if (format == Image_RGB_888) { // convert BGR->RGB for (UInt x = 0, wx = width*3; x < wx; x += 3) { UByte b = pcurScanline[x]; pcurScanline[x] = pcurScanline[x + 2]; pcurScanline[x + 2] = b; } } else if (destFormat == Image_P_8) { pimage->ColorMap.Resize(colorMap.NumEntries); for (UInt i = 0; i < colorMap.NumEntries; ++i) pimage->ColorMap[i].SetRGBA( colorMap.Entries[i].R, colorMap.Entries[i].G, colorMap.Entries[i].B, colorMap.Entries[i].A); } if (format != destFormat) { if (!ConvertScanline(pscanline, bitsPerPixel, format, srcScanLineSize, prgbData, destBitsPerPixel, destFormat, dstScanLineSize, &colorMap)) break; } --ysl; } if (pscanline && pscanline != scanlineBuf) GFREE(pscanline); if (y < height) // error occured return 0; pimage->AddRef(); } return pimage; } // Create and read a new image from the given filename, if possible. GImage* GImage::ReadJpeg(const char* filename, GJPEGSystem *psystem, GMemoryHeap* pimageHeap) { GSysFile in(filename); if (in.IsValid()) return ReadJpeg(&in, psystem, pimageHeap); return 0; } // Create and read a new image from the stream. GImage* GImage::ReadJpeg(GFile* pin, GJPEGSystem *psystem, GMemoryHeap* pimageHeap) { GJPEGInput* pjin = psystem->CreateInput(pin); if (!pjin) return 0; GImage* pimage; if (!pjin->IsErrorOccurred()) { pimage = CreateImage(Image_RGB_888, pjin->GetWidth(), pjin->GetHeight(), pimageHeap); if (pimage) { for (UInt y = 0; y < pimage->Height; y++) { if (!pjin->ReadScanline(pimage->GetScanline(y))) { pimage->Release(); pimage = NULL; break; } } } } else pimage = NULL; delete pjin; return pimage; } // *** DDS Format loading static const UByte* ParseUInt32(const UByte* buf, UInt32* pval) { *pval = GByteUtil::LEToSystem(*(UInt32*)buf); return buf + 4; } struct GImage_DDSFormatDescr { UInt32 RGBBitCount; UInt32 RBitMask; UInt32 GBitMask; UInt32 BBitMask; UInt32 ABitMask; bool HasAlpha; inline GImage_DDSFormatDescr() { RGBBitCount = RBitMask = GBitMask = BBitMask = ABitMask = 0; HasAlpha = false; } }; static bool GImage_ParseDDSHeader(GImageBase* pimage, const UByte* buf, const UByte** pdata, GImage_DDSFormatDescr* pDDSFmt) { enum { GFx_DDSD_CAPS =0x00000001l, GFx_DDSD_HEIGHT =0x00000002l, GFx_DDSD_WIDTH =0x00000004l, GFx_DDSD_PITCH =0x00000008l, GFx_DDSD_BACKBUFFERCOUNT =0x00000020l, GFx_DDSD_ZBUFFERBITDEPTH =0x00000040l, GFx_DDSD_ALPHABITDEPTH =0x00000080l, GFx_DDSD_LPSURFACE =0x00000800l, GFx_DDSD_PIXELFORMAT =0x00001000l, GFx_DDSD_CKDESTOVERLAY =0x00002000l, GFx_DDSD_CKDESTBLT =0x00004000l, GFx_DDSD_CKSRCOVERLAY =0x00008000l, GFx_DDSD_CKSRCBLT =0x00010000l, GFx_DDSD_MIPMAPCOUNT =0x00020000l, GFx_DDSD_REFRESHRATE =0x00040000l, GFx_DDSD_LINEARSIZE =0x00080000l, GFx_DDSD_TEXTURESTAGE =0x00100000l, GFx_DDSD_FVF =0x00200000l, GFx_DDSD_SRCVBHANDLE =0x00400000l, GFx_DDSD_DEPTH =0x00800000l }; UInt32 flags; UInt32 v; buf = ParseUInt32(buf, &flags); buf = ParseUInt32(buf, &v); if (flags & GFx_DDSD_HEIGHT) pimage->Height = v; buf = ParseUInt32(buf, &v); if (flags & GFx_DDSD_WIDTH) pimage->Width = v; buf = ParseUInt32(buf, &v); if (flags & GFx_DDSD_PITCH) pimage->Pitch = v; else if (flags & GFx_DDSD_LINEARSIZE) pimage->Pitch = v/pimage->Height*4; // Required by D3D10 buf = ParseUInt32(buf, &v); //if (flags & GFx_DDSD_DEPTH) // pimage->Depth = v; buf = ParseUInt32(buf, &v); if (flags & GFx_DDSD_MIPMAPCOUNT) pimage->MipMapCount = v; //buf = ParseUInt32(buf, &v); // alpha bit count //if (flags & GFx_DDSD_ALPHABITDEPTH) // pimage->AlphaBitDepth = v; buf += 11 * 4; if (flags & GFx_DDSD_PIXELFORMAT) { // pixel format (DDPIXELFORMAT) buf = ParseUInt32(buf, &v); // dwSize if (v != 32) // dwSize should be == 32 { GASSERT(0); return false; } enum GFx_DDPIXELFORMAT { GFx_DDPF_ALPHAPIXELS =0x00000001l, GFx_DDPF_ALPHA =0x00000002l, GFx_DDPF_FOURCC =0x00000004l, GFx_DDPF_PALETTEINDEXED4 =0x00000008l, GFx_DDPF_PALETTEINDEXEDTO8 =0x00000010l, GFx_DDPF_PALETTEINDEXED8 =0x00000020l, GFx_DDPF_RGB =0x00000040l, GFx_DDPF_COMPRESSED =0x00000080l }; UInt32 pfflags; buf = ParseUInt32(buf, &pfflags); // dwFlags buf = ParseUInt32(buf, &v); // dwFourCC if (pfflags & GFx_DDPF_FOURCC) { if (v == 0x35545844) // DXT5 pimage->Format = GImage::Image_DXT5; else if (v == 0x33545844) // DXT3 pimage->Format = GImage::Image_DXT3; else if (v == 0x31545844) // DXT1 pimage->Format = GImage::Image_DXT1; buf += 20; // skip remaining part of PixelFormat } else if ((pfflags & GFx_DDPF_RGB) || (pfflags & GFx_DDPF_ALPHA)) { // uncompressed DDS. Only 32-bit/24-bit RGB formats and alpha only (A8) are supported UInt32 bitCount; buf = ParseUInt32(buf, &bitCount); // dwRGBBitCount if (pDDSFmt) pDDSFmt->RGBBitCount = bitCount; switch(bitCount) { case 32: pimage->Format = GImage::Image_ARGB_8888; break; case 24: pimage->Format = GImage::Image_RGB_888; break; case 8: if (pfflags & GFx_DDPF_ALPHA) { pimage->Format = GImage::Image_A_8; break; } default: GASSERT(0); // unsupported } if (!(flags & GFx_DDSD_PITCH)) pimage->Pitch = pimage->Width*(bitCount/8); //AB: what is the Pitch in DDS for 24-bit RGB? buf = ParseUInt32(buf, &v); // dwRBitMask if (pDDSFmt) pDDSFmt->RBitMask = v; buf = ParseUInt32(buf, &v); // dwGBitMask if (pDDSFmt) pDDSFmt->GBitMask = v; buf = ParseUInt32(buf, &v); // dwBBitMask if (pDDSFmt) pDDSFmt->BBitMask = v; buf = ParseUInt32(buf, &v); // dwRGBAlphaBitMask if (pDDSFmt && (pfflags & GFx_DDPF_ALPHAPIXELS)) { pDDSFmt->ABitMask = v; pDDSFmt->HasAlpha = true; } // check for X8R8G8B8 - need to set alpha to 255 if (v == 0 && bitCount == 32) { GASSERT(0); // not supported for now. //@TODO - need to have one more Image_<> format for X8R8G8B8 } } GASSERT(pimage->Format != GImage::Image_None); // Unsupported format if (pimage->Format == GImage::Image_None) return false; } else buf += 32; buf += 16; // skip ddsCaps buf += 4; // skip reserved if (pdata) *pdata = buf; return true; } static UByte GFx_CalcShiftByMask(UInt32 mask) { UInt shifts = 0; if (mask == 0) return 0; if ((mask & 0xFFFFFFu) == 0) { mask >>= 24; shifts += 24; } else if ((mask & 0xFFFFu) == 0) { mask >>= 16; shifts += 16; } else if ((mask & 0xFFu) == 0) { mask >>= 8; shifts += 8; } while((mask & 1) == 0) { mask >>= 1; ++shifts; } return UByte(shifts); } static bool PostProcessUDDSData(GImage* pimage, const GImage_DDSFormatDescr& ddsFmt) { if (!pimage->IsDataCompressed() && (pimage->Format == GImage::Image_ARGB_8888 || pimage->Format == GImage::Image_RGB_888)) { UByte shiftR = UByte(GFx_CalcShiftByMask(ddsFmt.RBitMask)); UByte shiftG = UByte(GFx_CalcShiftByMask(ddsFmt.GBitMask)); UByte shiftB = UByte(GFx_CalcShiftByMask(ddsFmt.BBitMask)); UByte shiftA = UByte(GFx_CalcShiftByMask(ddsFmt.ABitMask)); // uncompressed DDS - reorganize RGBA in all mipmap levels for (UInt curlevel = 0; curlevel < pimage->MipMapCount; ++curlevel) { UInt w, h; UInt pitch; UByte* pimgData = pimage->GetMipMapLevelData(curlevel, &w, &h, &pitch); GASSERT(pimgData); // pimgData == NULL means DDS data is broken if (!pimgData) return false; for (UInt y = 0; y < h; y++) { UByte* p = pimgData + pitch*y; if (pimage->Format == GImage::Image_RGB_888) { for (UInt x = 0, wx = w*3; x < wx; x += 3) { UInt32 val = p[x + 0] | (UInt32(p[x + 1]) << 8) | (UInt32(p[x + 2]) << 16); p[x + 2] = UByte((val >> shiftB) & 0xFF); // B p[x + 1] = UByte((val >> shiftG) & 0xFF); // G p[x + 0] = UByte((val >> shiftR) & 0xFF); // R } } else if (pimage->Format == GImage::Image_ARGB_8888) { for (UInt x = 0, wx = w*4; x < wx; x += 4) { UInt32 val = p[x + 0] | (UInt32(p[x + 1]) << 8) | (UInt32(p[x + 2]) << 16) | (UInt32(p[x + 3]) << 24); p[x + 2] = UByte((val >> shiftB) & 0xFF); // B p[x + 1] = UByte((val >> shiftG) & 0xFF); // G p[x + 0] = UByte((val >> shiftR) & 0xFF); // R if (ddsFmt.HasAlpha) p[x + 3] = UByte((val >> shiftA) & 0xFF); // A else p[x + 3] = 0xFF; } } } } } return true; } // Loads DDS from file creating GImage. GImage* GImage::ReadDDS(GFile* pin, GMemoryHeap* pheap) { if (!pin || !pin->IsValid()) return NULL; // First, read and verify the header. GImageBase imageBase; GImage * pimage = 0; SInt fileSize = pin->GetLength(); UInt32 fourcc = pin->ReadUInt32(); if (fourcc != 0x20534444) // 'D','D','S',' ' return 0; UInt32 sz = pin->ReadUInt32(); if (sz != 124) return 0; UByte buf[256]; if (pin->Read(buf, 120) != 120) return 0; imageBase.ClearImageBase(); GImage_DDSFormatDescr ddsFmt; if (!GImage_ParseDDSHeader(&imageBase, buf, 0, &ddsFmt)) return 0; // Allocate image and read-in data. if ((pimage = new GImage())==0) return 0; pimage->Format = imageBase.Format; pimage->Height = imageBase.Height; pimage->Width = imageBase.Width; pimage->Pitch = imageBase.Pitch; pimage->MipMapCount = imageBase.MipMapCount; if (!pheap) pheap = GMemory::GetGlobalHeap(); SInt dataSize = fileSize - pin->Tell(); UByte* pdata = (UByte*)GHEAP_ALLOC(pheap, dataSize, GStat_Image_Mem); if (!pdata) { pimage->Release(); return 0; } if (pin->Read(pdata, dataSize) != dataSize) { pimage->Release(); GFREE(pdata); return 0; } // AB: do we need to do same for uncompressed DDS? #ifdef GFC_OS_XBOX360 if (pimage->IsDataCompressed()) { // We need to convert byte order for XBox360. This does not apply // to other big-endian systems such as PS3. UInt16 *pidata = (UInt16*)pdata; SInt i; for (i=0; ipData = pdata; pimage->DataSize = dataSize; if (!PostProcessUDDSData(pimage, ddsFmt)) { pimage->Release(); return 0; } return pimage; } // Loads DDS from a chunk of memory, data is copied. GImage* GImage::ReadDDSFromMemory(const UByte* ddsData, UPInt dataSize, GMemoryHeap* pheap) { GImage * pimage = 0; const UByte* pdata; UInt32 fourcc; ddsData = ParseUInt32(ddsData, &fourcc); if (fourcc != 0x20534444) // 'D','D','S',' ' return 0; UInt32 sz; ddsData = ParseUInt32(ddsData, &sz); if (sz != 124) return 0; if ((pimage = new GImage()) == 0) return 0; GImage_DDSFormatDescr ddsFmt; if (!GImage_ParseDDSHeader(pimage, ddsData, &pdata, &ddsFmt)) { pimage->Release(); return 0; } // Alloc data and copy it. if (!pheap) pheap = GMemory::GetGlobalHeap(); pimage->DataSize = (UInt)(dataSize - (pdata - ddsData)); pimage->pData = (UByte*)GHEAP_ALLOC(pheap, pimage->DataSize, GStat_Image_Mem); if (!pimage->pData) { pimage->Release(); return 0; } memcpy(pimage->pData, pdata, pimage->DataSize); // AB: do we need to do same for uncompressed DDS? I guess - not. #ifdef GFC_OS_XBOX360 if (pimage->IsDataCompressed()) { // We need to convert byte order for XBox360. This does not apply // to other big-endian systems such as PS3. UInt16 *pidata = (UInt16*)pimage->pData; UInt i; for (i = 0; i < pimage->DataSize/2; i++) { *pidata = GByteUtil::LEToSystem(*pidata); pidata++; } } #endif if (!PostProcessUDDSData(pimage, ddsFmt)) { pimage->Release(); return 0; } return pimage; } #ifndef GFC_USE_LIBPNG // Create and read a new image from the given filename, if possible. GImage* GImage::ReadPng(const char* filename, GMemoryHeap* pimageHeap) { GUNUSED2(filename, pimageHeap); GFC_DEBUG_WARNING(1, "GImage::ReadPng failed - GFC_USE_LIBPNG not defined"); return NULL; } // Create and read a new image from the stream. GImage* GImage::ReadPng(GFile* pin, GMemoryHeap* pimageHeap) { GUNUSED2(pin, pimageHeap); GFC_DEBUG_WARNING(1, "GImage::ReadPng failed - GFC_USE_LIBPNG not defined"); return NULL; } bool WritePng(GFile* pout) { GUNUSED(pout); GFC_DEBUG_WARNING(1, "GImage::WritePng failed - GFC_USE_LIBPNG not defined"); return false; } #endif