diff --git a/Source/DiabloUI/art.cpp b/Source/DiabloUI/art.cpp index 71f59fc26..36f44f92d 100644 --- a/Source/DiabloUI/art.cpp +++ b/Source/DiabloUI/art.cpp @@ -1,10 +1,103 @@ +#include "art.h" + +#include +#include +#include + #include "DiabloUI/art.h" #include "storm/storm.h" #include "utils/display.h" -#include "utils/sdl_compat.h" #include "utils/log.hpp" +#include "utils/sdl_compat.h" namespace devilution { +namespace { +constexpr unsigned PcxHeaderSize = 128; +constexpr unsigned NumPaletteColors = 256; +constexpr unsigned PcxPaletteSize = 1 + NumPaletteColors * 3; + +bool LoadPcxMeta(HANDLE handle, int &width, int &height, std::uint8_t &bpp) +{ + PCXHeader pcxhdr; + if (!SFileReadFile(handle, &pcxhdr, PcxHeaderSize, nullptr, nullptr)) { + return false; + } + width = SDL_SwapLE16(pcxhdr.Xmax) - SDL_SwapLE16(pcxhdr.Xmin) + 1; + height = SDL_SwapLE16(pcxhdr.Ymax) - SDL_SwapLE16(pcxhdr.Ymin) + 1; + bpp = pcxhdr.BitsPerPixel; + return true; +} + +bool LoadPcxPixelsAndPalette(HANDLE handle, int width, int height, std::uint8_t bpp, + BYTE *buffer, std::size_t bufferPitch, SDL_Color *palette) +{ + const bool has256ColorPalette = palette != nullptr && bpp == 8; + std::uint32_t pixelDataSize = SFileGetFileSize(handle, nullptr); + if (pixelDataSize == static_cast(-1)) { + return false; + } + pixelDataSize -= PcxHeaderSize + (has256ColorPalette ? PcxPaletteSize : 0); + + // We read 1 extra byte because it delimits the palette. + const unsigned readSize = pixelDataSize + (has256ColorPalette ? PcxPaletteSize : 0); + std::unique_ptr fileBuffer = std::make_unique(readSize); + if (!SFileReadFile(handle, fileBuffer.get(), readSize, nullptr, nullptr)) { + return false; + } + const unsigned xSkip = bufferPitch - width; + BYTE *dataPtr = fileBuffer.get(); + for (int j = 0; j < height; j++) { + for (int x = 0; x < width;) { + constexpr std::uint8_t PcxMaxSinglePixel = 0xBF; + const std::uint8_t byte = *dataPtr++; + if (byte <= PcxMaxSinglePixel) { + *buffer++ = byte; + ++x; + continue; + } + constexpr std::uint8_t PcxRunLengthMask = 0x3F; + const std::uint8_t runLength = (byte & PcxRunLengthMask); + std::memset(buffer, *dataPtr++, runLength); + buffer += runLength; + x += runLength; + } + buffer += xSkip; + } + + if (has256ColorPalette) { + [[maybe_unused]] constexpr unsigned PcxPaletteSeparator = 0x0C; + assert(*dataPtr == PcxPaletteSeparator); + ++dataPtr; + + auto *out = palette; + for (unsigned i = 0; i < NumPaletteColors; ++i) { + out->r = *dataPtr++; + out->g = *dataPtr++; + out->b = *dataPtr++; +#ifndef USE_SDL1 + out->a = SDL_ALPHA_OPAQUE; +#endif + ++out; + } + } + return true; +} + +Uint32 GetPcxSdlPixelFormat(unsigned bpp) +{ + switch (bpp) { + case 8: // NOLINT(readability-magic-numbers) + return SDL_PIXELFORMAT_INDEX8; + case 24: // NOLINT(readability-magic-numbers) + return SDL_PIXELFORMAT_RGB888; + case 32: // NOLINT(readability-magic-numbers) + return SDL_PIXELFORMAT_RGBA8888; + default: + return 0; + } +} + +} // namespace void LoadArt(const char *pszFile, Art *art, int frames, SDL_Color *pPalette) { @@ -13,34 +106,28 @@ void LoadArt(const char *pszFile, Art *art, int frames, SDL_Color *pPalette) art->frames = frames; - DWORD width, height, bpp; - if (!SBmpLoadImage(pszFile, nullptr, nullptr, 0, &width, &height, &bpp)) { - Log("Failed to load image meta"); + HANDLE handle; + int width; + int height; + std::uint8_t bpp; + if (!SFileOpenFile(pszFile, &handle)) { return; } - Uint32 format; - switch (bpp) { - case 8: - format = SDL_PIXELFORMAT_INDEX8; - break; - case 24: - format = SDL_PIXELFORMAT_RGB888; - break; - case 32: - format = SDL_PIXELFORMAT_RGBA8888; - break; - default: - format = 0; - break; + if (!LoadPcxMeta(handle, width, height, bpp)) { + Log("LoadArt(\"{}\"): LoadPcxMeta failed with code {}", pszFile, SErrGetLastError()); + SFileCloseFile(handle); + return; } - SDLSurfaceUniquePtr artSurface { SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE, width, height, bpp, format) }; - if (!SBmpLoadImage(pszFile, pPalette, static_cast(artSurface->pixels), - artSurface->pitch * artSurface->format->BytesPerPixel * height, nullptr, nullptr, nullptr)) { - Log("Failed to load image"); + SDLSurfaceUniquePtr artSurface { SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE, width, height, bpp, GetPcxSdlPixelFormat(bpp)) }; + if (!LoadPcxPixelsAndPalette(handle, width, height, bpp, static_cast(artSurface->pixels), + artSurface->pitch, pPalette)) { + Log("LoadArt(\"{}\"): LoadPcxPixelsAndPalette failed with code {}", pszFile, SErrGetLastError()); + SFileCloseFile(handle); return; } + SFileCloseFile(handle); art->logical_width = artSurface->w; art->frame_height = height / frames; @@ -57,9 +144,11 @@ void LoadMaskedArt(const char *pszFile, Art *art, int frames, int mask) void LoadArt(Art *art, const BYTE *artData, int w, int h, int frames) { + constexpr int DefaultArtBpp = 8; + constexpr int DefaultArtFormat = SDL_PIXELFORMAT_INDEX8; art->frames = frames; art->surface = ScaleSurfaceToOutput(SDLSurfaceUniquePtr { SDL_CreateRGBSurfaceWithFormatFrom( - const_cast(artData), w, h, 8, w, SDL_PIXELFORMAT_INDEX8) }); + const_cast(artData), w, h, DefaultArtBpp, w, DefaultArtFormat) }); art->logical_width = w; art->frame_height = h / frames; } diff --git a/Source/storm/storm.cpp b/Source/storm/storm.cpp index 85d611681..a0601a726 100644 --- a/Source/storm/storm.cpp +++ b/Source/storm/storm.cpp @@ -120,135 +120,14 @@ bool SFileOpenFile(const char *filename, HANDLE *phFile) } if (!result || (*phFile == nullptr)) { - Log("{}: Not found: {}", __FUNCTION__, filename); - } - return result; -} - -bool SBmpLoadImage(const char *pszFileName, SDL_Color *pPalette, BYTE *pBuffer, DWORD dwBuffersize, DWORD *pdwWidth, DWORD *dwHeight, DWORD *pdwBpp) -{ - HANDLE hFile; - size_t size; - PCXHeader pcxhdr; - BYTE paldata[256][3]; - BYTE *dataPtr, *fileBuffer; - BYTE byte; - - if (pdwWidth != nullptr) - *pdwWidth = 0; - if (dwHeight != nullptr) - *dwHeight = 0; - if (pdwBpp != nullptr) - *pdwBpp = 0; - - if (!pszFileName || !*pszFileName) { - return false; - } - - if (pBuffer && !dwBuffersize) { - return false; - } - - if (!pPalette && !pBuffer && !pdwWidth && !dwHeight) { - return false; - } - - if (!SFileOpenFile(pszFileName, &hFile)) { - return false; - } - - while (strchr(pszFileName, 92) != nullptr) - pszFileName = strchr(pszFileName, 92) + 1; - - while (strchr(pszFileName + 1, 46) != nullptr) - pszFileName = strchr(pszFileName, 46); - - // omit all types except PCX - if (!pszFileName || strcasecmp(pszFileName, ".pcx") != 0) { - return false; - } - - if (!SFileReadFile(hFile, &pcxhdr, 128, nullptr, nullptr)) { - SFileCloseFile(hFile); - return false; - } - - int width = SDL_SwapLE16(pcxhdr.Xmax) - SDL_SwapLE16(pcxhdr.Xmin) + 1; - int height = SDL_SwapLE16(pcxhdr.Ymax) - SDL_SwapLE16(pcxhdr.Ymin) + 1; - - // If the given buffer is larger than width * height, assume the extra data - // is scanline padding. - // - // This is useful because in SDL the pitch size is often slightly larger - // than image width for efficiency. - const int xSkip = dwBuffersize / height - width; - - if (pdwWidth != nullptr) - *pdwWidth = width; - if (dwHeight != nullptr) - *dwHeight = height; - if (pdwBpp != nullptr) - *pdwBpp = pcxhdr.BitsPerPixel; - - if (pBuffer == nullptr) { - SFileSetFilePointer(hFile, 0, nullptr, DVL_FILE_END); - fileBuffer = nullptr; - } else { - const auto pos = SFileGetFilePointer(hFile); - const auto end = SFileSetFilePointer(hFile, 0, DVL_FILE_END); - const auto begin = SFileSetFilePointer(hFile, pos, DVL_FILE_BEGIN); - size = end - begin; - fileBuffer = (BYTE *)malloc(size); - } - - if (fileBuffer != nullptr) { - SFileReadFile(hFile, fileBuffer, size, nullptr, nullptr); - dataPtr = fileBuffer; - - for (int j = 0; j < height; j++) { - for (int x = 0; x < width; dataPtr++) { - byte = *dataPtr; - if (byte < 0xC0) { - *pBuffer = byte; - pBuffer++; - x++; - continue; - } - dataPtr++; - - for (int i = 0; i < (byte & 0x3F); i++) { - *pBuffer = *dataPtr; - pBuffer++; - x++; - } - } - // Skip the pitch padding. - pBuffer += xSkip; + const auto error = SErrGetLastError(); + if (error == STORM_ERROR_FILE_NOT_FOUND) { + LogVerbose("{}(\"{}\") File not found", __FUNCTION__, filename); + } else { + LogError("{}(\"{}\") Failed with error code {}", __FUNCTION__, filename, error); } - - free(fileBuffer); } - - if (pPalette && pcxhdr.BitsPerPixel == 8) { - const auto pos = SFileSetFilePointer(hFile, -768, DVL_FILE_CURRENT); - if (pos == static_cast(-1)) { - Log("SFileSetFilePointer error: {}", (unsigned int)SErrGetLastError()); - } - SFileReadFile(hFile, paldata, 768, nullptr, nullptr); - - for (int i = 0; i < 256; i++) { - pPalette[i].r = paldata[i][0]; - pPalette[i].g = paldata[i][1]; - pPalette[i].b = paldata[i][2]; -#ifndef USE_SDL1 - pPalette[i].a = SDL_ALPHA_OPAQUE; -#endif - } - } - - SFileCloseFile(hFile); - - return true; + return result; } bool getIniBool(const char *sectionName, const char *keyName, bool defaultValue) diff --git a/Source/storm/storm.h b/Source/storm/storm.h index 7e627f756..bc7eec4d1 100644 --- a/Source/storm/storm.h +++ b/Source/storm/storm.h @@ -252,29 +252,6 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, uint32_t *lpFileSizeHigh); DWORD WINAPI SFileSetFilePointer(HANDLE, int, int *, int); bool WINAPI SFileCloseFile(HANDLE hFile); -/* SBmpLoadImage @ 323 - * - * Load an image from an available archive into a buffer. - * - * pszFileName: The name of the graphic in an active archive. - * pPalette: An optional buffer that receives the image palette. - * pBuffer: A buffer that receives the image data. - * dwBuffersize: The size of the specified image buffer. - * pdwWidth: An optional variable that receives the image width. - * pdwHeight: An optional variable that receives the image height. - * pdwBpp: An optional variable that receives the image bits per pixel. - * - * Returns true if the image was supported and loaded correctly, false otherwise. - */ -bool SBmpLoadImage( - const char *pszFileName, - SDL_Color *pPalette, - BYTE *pBuffer, - DWORD dwBuffersize, - DWORD *pdwWidth, - DWORD *pdwHeight, - DWORD *pdwBpp); - bool getIniBool(const char *sectionName, const char *keyName, bool defaultValue = false); float getIniFloat(const char *sectionName, const char *keyName, float defaultValue); bool getIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString = "");