diff --git a/CMakeLists.txt b/CMakeLists.txt index 3808cbe1..00ba25ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -473,6 +473,9 @@ if (ISLE_BUILD_APP) ISLE/res/isle.rc ISLE/isleapp.cpp ISLE/islefiles.cpp + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/busy_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/no_bmp.h ) list(APPEND isle_targets isle) if (WIN32) @@ -529,6 +532,39 @@ if (ISLE_BUILD_APP) ISLE/3ds/config.cpp ) endif() + if(Python3_FOUND) + if(NOT DEFINED PYTHON_PIL_AVAILABLE) + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import PIL; print('pil')" + RESULT_VARIABLE PIL_RESULT + OUTPUT_VARIABLE PIL_OUTPUT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(PIL_RESULT EQUAL 0 AND PIL_OUTPUT STREQUAL "pil") + set(PIL_AVAILABLE 1) + else() + message(STATUS "Python PIL not found, using pre-generated headers.") + set(PIL_AVAILABLE 0) + endif() + set(PYTHON_PIL_AVAILABLE ${PIL_AVAILABLE} CACHE BOOL "Is Python3 Pillow available?") + endif() + if(PYTHON_PIL_AVAILABLE) + add_custom_command( + OUTPUT + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/busy_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/no_bmp.h + COMMAND ${Python3_EXECUTABLE} tools/curpng2h.py ISLE/res/arrow.png ISLE/res/busy.png ISLE/res/no.png + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS + ${CMAKE_SOURCE_DIR}/tools/curpng2h.py + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow.png + ${CMAKE_SOURCE_DIR}/ISLE/res/busy.png + ${CMAKE_SOURCE_DIR}/ISLE/res/no.png + ) + endif() + endif() endif() if (ISLE_BUILD_CONFIG) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e12f9183..cfa3c50a 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -28,7 +28,10 @@ #include "mxtransitionmanager.h" #include "mxutilities.h" #include "mxvariabletable.h" +#include "res/arrow_bmp.h" +#include "res/busy_bmp.h" #include "res/isle_bmp.h" +#include "res/no_bmp.h" #include "res/resource.h" #include "roi/legoroi.h" #include "tgl/d3drm/impl.h" @@ -135,6 +138,10 @@ IsleApp::IsleApp() m_cursorBusy = NULL; m_cursorNo = NULL; m_cursorCurrent = NULL; + m_cursorArrowBitmap = NULL; + m_cursorBusyBitmap = NULL; + m_cursorNoBitmap = NULL; + m_cursorCurrentBitmap = NULL; LegoOmni::CreateInstance(); @@ -656,6 +663,12 @@ MxResult IsleApp::SetupWindow() m_cursorBusy = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); m_cursorNo = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); SDL_SetCursor(m_cursorCurrent); + if (g_isle->GetDrawCursor()) { + SDL_HideCursor(); + m_cursorCurrentBitmap = m_cursorArrowBitmap = &arrow_cursor; + m_cursorBusyBitmap = &busy_cursor; + m_cursorNoBitmap = &no_cursor; + } SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, g_targetWidth); @@ -739,6 +752,9 @@ MxResult IsleApp::SetupWindow() LegoOmni::GetInstance()->GetInputManager()->SetUseJoystick(m_useJoystick); LegoOmni::GetInstance()->GetInputManager()->SetJoystickIndex(m_joystickIndex); } + if (LegoOmni::GetInstance()->GetVideoManager() && g_isle->GetDrawCursor()) { + LegoOmni::GetInstance()->GetVideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); + } MxDirect3D* d3d = LegoOmni::GetInstance()->GetVideoManager()->GetDirect3D(); if (d3d) { SDL_Log( @@ -1023,15 +1039,19 @@ void IsleApp::SetupCursor(Cursor p_cursor) switch (p_cursor) { case e_cursorArrow: m_cursorCurrent = m_cursorArrow; + m_cursorCurrentBitmap = m_cursorArrowBitmap; break; case e_cursorBusy: m_cursorCurrent = m_cursorBusy; + m_cursorCurrentBitmap = m_cursorBusyBitmap; break; case e_cursorNo: m_cursorCurrent = m_cursorNo; + m_cursorCurrentBitmap = m_cursorNoBitmap; break; case e_cursorNone: m_cursorCurrent = NULL; + m_cursorCurrentBitmap = NULL; case e_cursorUnused3: case e_cursorUnused4: case e_cursorUnused5: @@ -1043,12 +1063,22 @@ void IsleApp::SetupCursor(Cursor p_cursor) break; } - if (m_cursorCurrent != NULL) { - SDL_SetCursor(m_cursorCurrent); - SDL_ShowCursor(); + if (g_isle->GetDrawCursor()) { + if (m_cursorCurrentBitmap == NULL) { + VideoManager()->SetCursorBitmap(NULL); + } + else { + VideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); + } } else { - SDL_HideCursor(); + if (m_cursorCurrent != NULL) { + SDL_SetCursor(m_cursorCurrent); + SDL_ShowCursor(); + } + else { + SDL_HideCursor(); + } } } diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index d0a7f523..4064dc25 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -1,6 +1,7 @@ #ifndef ISLEAPP_H #define ISLEAPP_H +#include "cursor.h" #include "lego1_export.h" #include "legoutils.h" #include "mxtransitionmanager.h" @@ -87,6 +88,10 @@ private: SDL_Cursor* m_cursorBusy; // 0x80 SDL_Cursor* m_cursorNo; // 0x84 SDL_Cursor* m_cursorCurrent; // 0x88 + const CursorBitmap* m_cursorArrowBitmap; + const CursorBitmap* m_cursorBusyBitmap; + const CursorBitmap* m_cursorNoBitmap; + const CursorBitmap* m_cursorCurrentBitmap; char* m_mediaPath; char* m_iniPath; diff --git a/ISLE/res/arrow.png b/ISLE/res/arrow.png new file mode 100644 index 00000000..eaa08ca6 Binary files /dev/null and b/ISLE/res/arrow.png differ diff --git a/ISLE/res/arrow_bmp.h b/ISLE/res/arrow_bmp.h new file mode 100644 index 00000000..84e3452d --- /dev/null +++ b/ISLE/res/arrow_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/arrow.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char arrow_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x41, 0xF0, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, + 0x54, 0x80, 0x00, 0x00, 0x64, 0x80, 0x00, 0x00, 0x42, 0x40, 0x00, 0x00, + 0x02, 0x40, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char arrow_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, + 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, + 0x7F, 0xE0, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, + 0x77, 0x80, 0x00, 0x00, 0x67, 0x80, 0x00, 0x00, 0x43, 0xC0, 0x00, 0x00, + 0x03, 0xC0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap arrow_cursor = { 32, 32, arrow_data, arrow_mask }; diff --git a/ISLE/res/busy.png b/ISLE/res/busy.png new file mode 100644 index 00000000..cd8f5bf1 Binary files /dev/null and b/ISLE/res/busy.png differ diff --git a/ISLE/res/busy_bmp.h b/ISLE/res/busy_bmp.h new file mode 100644 index 00000000..020512bc --- /dev/null +++ b/ISLE/res/busy_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/busy.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char busy_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x40, 0x01, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x20, 0x02, 0x00, + 0x00, 0x20, 0x02, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x22, 0xA2, 0x00, + 0x00, 0x11, 0x44, 0x00, 0x00, 0x08, 0x88, 0x00, 0x00, 0x04, 0x10, 0x00, + 0x00, 0x02, 0x20, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x02, 0x20, 0x00, + 0x00, 0x02, 0x20, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x08, 0x88, 0x00, + 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x21, 0x42, 0x00, + 0x00, 0x22, 0xA2, 0x00, 0x00, 0x25, 0x52, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x40, 0x01, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char busy_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x07, 0xF0, 0x00, + 0x00, 0x03, 0xE0, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x03, 0xE0, 0x00, + 0x00, 0x03, 0xE0, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x0F, 0xF8, 0x00, + 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap busy_cursor = { 32, 32, busy_data, busy_mask }; diff --git a/ISLE/res/no.png b/ISLE/res/no.png new file mode 100644 index 00000000..968adccd Binary files /dev/null and b/ISLE/res/no.png differ diff --git a/ISLE/res/no_bmp.h b/ISLE/res/no_bmp.h new file mode 100644 index 00000000..a4ebe829 --- /dev/null +++ b/ISLE/res/no_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/no.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char no_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0xF8, 0x00, + 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0xF8, 0x07, 0x00, + 0x00, 0xDC, 0x03, 0x00, 0x01, 0xCE, 0x03, 0x80, 0x01, 0x87, 0x01, 0x80, + 0x01, 0x83, 0x81, 0x80, 0x01, 0x81, 0xC1, 0x80, 0x01, 0x80, 0xE1, 0x80, + 0x01, 0xC0, 0x73, 0x80, 0x00, 0xC0, 0x3B, 0x00, 0x00, 0xE0, 0x1F, 0x00, + 0x00, 0x70, 0x0E, 0x00, 0x00, 0x3C, 0x1C, 0x00, 0x00, 0x1F, 0xF8, 0x00, + 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char no_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x3F, 0xFC, 0x00, + 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x01, 0xFC, 0x0F, 0x80, + 0x01, 0xFE, 0x07, 0x80, 0x03, 0xFF, 0x07, 0xC0, 0x03, 0xCF, 0x83, 0xC0, + 0x03, 0xC7, 0xC3, 0xC0, 0x03, 0xC3, 0xE3, 0xC0, 0x03, 0xC1, 0xF3, 0xC0, + 0x03, 0xE0, 0xFF, 0xC0, 0x01, 0xE0, 0x7F, 0x80, 0x01, 0xF0, 0x3F, 0x80, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x3F, 0xFC, 0x00, + 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap no_cursor = { 32, 32, no_data, no_mask }; diff --git a/LEGO1/cursor.h b/LEGO1/cursor.h new file mode 100644 index 00000000..171972c2 --- /dev/null +++ b/LEGO1/cursor.h @@ -0,0 +1,8 @@ +#pragma once + +typedef struct CursorBitmap { + int width; + int height; + const unsigned char* data; + const unsigned char* mask; +} CursorBitmap; diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h index 6c12d863..ba96541c 100644 --- a/LEGO1/lego/legoomni/include/legovideomanager.h +++ b/LEGO1/lego/legoomni/include/legovideomanager.h @@ -1,6 +1,7 @@ #ifndef LEGOVIDEOMANAGER_H #define LEGOVIDEOMANAGER_H +#include "cursor.h" #include "decomp.h" #include "lego1_export.h" #include "legophonemelist.h" @@ -37,6 +38,7 @@ public: void EnableFullScreenMovie(MxBool p_enable); LEGO1_EXPORT void EnableFullScreenMovie(MxBool p_enable, MxBool p_scale); LEGO1_EXPORT void MoveCursor(MxS32 p_cursorX, MxS32 p_cursorY); + LEGO1_EXPORT void SetCursorBitmap(const CursorBitmap* p_cursorBitmap); void ToggleFPS(MxBool p_visible); MxResult Tickle() override; // vtable+0x08 diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index 7e2f7e42..8b92065c 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -272,14 +272,13 @@ void LegoVideoManager::MoveCursor(MxS32 p_cursorX, MxS32 p_cursorY) { m_cursorX = p_cursorX; m_cursorY = p_cursorY; - m_drawCursor = TRUE; - if (623 < p_cursorX) { - m_cursorX = 623; + if (640 < p_cursorX) { + m_cursorX = 640; } - if (463 < p_cursorY) { - m_cursorY = 463; + if (480 < p_cursorY) { + m_cursorY = 480; } } @@ -836,3 +835,30 @@ void LegoVideoManager::DrawTextToSurface32( ++p_text; } } + +void LegoVideoManager::SetCursorBitmap(const CursorBitmap* p_cursorBitmap) +{ + if (p_cursorBitmap == NULL) { + m_drawCursor = FALSE; + return; + } + + if (m_cursorSurface != NULL) { + m_cursorSurface->Release(); + m_cursorSurface = NULL; + } + + m_cursorRect.top = 0; + m_cursorRect.left = 0; + m_cursorRect.bottom = p_cursorBitmap->height; + m_cursorRect.right = p_cursorBitmap->width; + + m_cursorSurface = MxDisplaySurface::CreateCursorSurface(p_cursorBitmap); + + if (m_cursorSurface == NULL) { + m_drawCursor = FALSE; + return; + } + + m_drawCursor = TRUE; +} diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index 8d31ff4f..ec9c6aa5 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -1,6 +1,7 @@ #ifndef MXDISPLAYSURFACE_H #define MXDISPLAYSURFACE_H +#include "cursor.h" #include "decomp.h" #include "mxcore.h" #include "mxvideoparam.h" @@ -97,6 +98,7 @@ public: void ClearScreen(); static LPDIRECTDRAWSURFACE CreateCursorSurface(); + static LPDIRECTDRAWSURFACE CreateCursorSurface(const CursorBitmap* p_cursorBitmap); static LPDIRECTDRAWSURFACE CopySurface(LPDIRECTDRAWSURFACE p_src); LPDIRECTDRAWSURFACE GetDirectDrawSurface1() { return m_ddSurface1; } diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index badf05d1..bcd38000 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1296,3 +1296,125 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::FUN_100bc8b0(MxS32 p_width, MxS32 p_height return surface; } + +LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_cursorBitmap) +{ + LPDIRECTDRAWSURFACE newSurface = NULL; + IDirectDraw* draw = MVideoManager()->GetDirectDraw(); + MVideoManager(); + + DDSURFACEDESC ddsd; + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + if (draw->GetDisplayMode(&ddsd) != DD_OK) { + return NULL; + } + + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + + ddsd.dwWidth = p_cursorBitmap->width; + ddsd.dwHeight = p_cursorBitmap->height; + ddsd.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OFFSCREENPLAIN; + + if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { + ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY; + ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; + + if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { + goto done; + } + } + + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + if (newSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) != DD_OK) { + goto done; + } + else { + for (int y = 0; y < p_cursorBitmap->height; y++) { + for (int x = 0; x < p_cursorBitmap->width; x++) { + MxS32 bitIndex = y * p_cursorBitmap->width + x; + MxS32 byteIndex = bitIndex / 8; + MxS32 bitOffset = 7 - (bitIndex % 8); + + MxBool isOpaque = (p_cursorBitmap->mask[byteIndex] >> bitOffset) & 1; + MxBool isBlack = (p_cursorBitmap->data[byteIndex] >> bitOffset) & 1; + + switch (bytesPerPixel) { + case 1: { + MxU8* surface = (MxU8*) ddsd.lpSurface; + + MxU8 pixel; + if (!isOpaque) { + pixel = 0x10; + } + else { + pixel = isBlack ? 0 : 0xff; + } + } + case 2: { + MxU16* surface = (MxU16*) ddsd.lpSurface; + + MxU16 pixel; + if (!isOpaque) { + pixel = RGB555_CREATE(0x1f, 0, 0x1f); + } + else { + pixel = isBlack ? RGB555_CREATE(0, 0, 0) : RGB555_CREATE(0x1f, 0x1f, 0x1f); + } + + surface[x + y * p_cursorBitmap->width] = pixel; + break; + } + default: { + MxU32* surface = (MxU32*) ddsd.lpSurface; + + MxS32 pixel; + if (!isOpaque) { + pixel = RGB8888_CREATE(0, 0, 0, 0); // Transparent pixel + } + else { + pixel = isBlack ? RGB8888_CREATE(0, 0, 0, 0xff) : RGB8888_CREATE(0xff, 0xff, 0xff, 0xff); + } + + surface[x + y * p_cursorBitmap->width] = pixel; + break; + } + } + } + } + + newSurface->Unlock(ddsd.lpSurface); + switch (bytesPerPixel) { + case 1: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = 0x10; + colorkey.dwColorSpaceLowValue = 0x10; + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + break; + } + case 2: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); + colorkey.dwColorSpaceLowValue = RGB555_CREATE(0x1f, 0, 0x1f); + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + break; + } + default: { + break; + } + } + + return newSurface; + } + +done: + if (newSurface) { + newSurface->Release(); + } + + return NULL; +} diff --git a/tools/curpng2h.py b/tools/curpng2h.py new file mode 100755 index 00000000..d55231b0 --- /dev/null +++ b/tools/curpng2h.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import argparse +import itertools +from PIL import Image +from pathlib import Path + + +def encode_cursor(image_path: Path): + img = Image.open(image_path).convert("RGBA") + width, height = img.size + pixels = img.load() + + num_pixels = width * height + num_bytes = (num_pixels + 7) // 8 + + data = bytearray(num_bytes) + mask = bytearray(num_bytes) + + for y in range(height): + for x in range(width): + i = y * width + x + byte_index = i // 8 + bit_offset = 7 - (i % 8) + + r, g, b, a = pixels[x, y] + + if a >= 128: + mask[byte_index] |= 1 << bit_offset # opaque + lum = int(0.299 * r + 0.587 * g + 0.114 * b) + if lum < 128: + data[byte_index] |= 1 << bit_offset # black pixel + + return data, mask, width, height + + +def to_c_array(name, data): + lines = [] + for rowdata in itertools.batched(data, 12): + lines.append(", ".join(f"0x{byte:02X}" for byte in rowdata) + ",") + array_str = "\n ".join(lines) + return f"static const unsigned char {name}[] = {{\n {array_str}\n}};\n" + + +def main(): + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("inputs", nargs="+", help="PNG images", type=Path) + args = parser.parse_args() + + input_files: list[Path] = args.inputs + + for input_file in input_files: + data, mask, width, height = encode_cursor(input_file) + + input_file_name = input_file.stem + output_file = input_file.with_name(f"{input_file_name}_bmp.h") + + with output_file.open("w", newline="\n") as f: + f.write(f"#pragma once\n\n") + f.write(f"// Generated from {input_file}\n") + f.write(f"// Dimensions: {width}x{height}\n") + f.write("// This file is auto-generated, do not edit it.\n\n") + f.write(f'#include "cursor.h"\n\n') + f.write(to_c_array(f"{input_file_name}_data", data)) + f.write("\n") + f.write(to_c_array(f"{input_file_name}_mask", mask)) + f.write("\n") + f.write( + f"static const CursorBitmap {input_file_name}_cursor = {'{'} {width}, {height}, {input_file_name}_data, {input_file_name}_mask {'}'};\n" + ) + + print(f"Written {output_file} with cursor data.") + + +if __name__ == "__main__": + raise SystemExit(main())