alien_fx_fiend

Infinite Canvas Doodle App (Performance Optimizations *FINAL RELEASE*

Nov 30th, 2024
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 39.75 KB | Source Code | 0 0
  1. ==++ Here's the source code for (file 1/1) "main.cpp":: ++==
  2. #define NOMINMAX
  3. #define WIN32_LEAN_AND_MEAN
  4.  
  5. #include <windows.h>
  6. #include <windef.h>
  7. #include <wingdi.h>
  8. #include <algorithm>
  9. #include <iostream>
  10. #include <vector>
  11. #include <windowsx.h> // For GET_X_LPARAM and GET_Y_LPARAM
  12. #include <cmath>
  13. #include <commctrl.h>
  14. #include <commdlg.h>  
  15. #include <stdio.h>
  16. #include <cmath>
  17. #include <fstream>
  18. #include <thread>
  19. #include <mutex>
  20. #include "resource.h"
  21.  
  22. #pragma comment(lib, "comctl32.lib")
  23. #pragma comment(lib, "comdlg32.lib")
  24. #pragma comment(lib, "msimg32.lib")
  25.  
  26. using namespace std;
  27.  
  28. struct DrawPoint {
  29.    int x, y;
  30.    DWORD timestamp;
  31.    DrawPoint() : x(0), y(0), timestamp(0) {}
  32.    DrawPoint(int px, int py) : x(px), y(py), timestamp(GetTickCount()) {}
  33. };
  34.  
  35. struct SerializedStroke {
  36.    std::vector<DrawPoint> points;
  37.    COLORREF color;
  38.    int brushSize;
  39.    bool isEraser;
  40. };
  41.  
  42. struct CanvasState {
  43.    std::vector<SerializedStroke> strokes;
  44.    float gridZoomFactor;
  45.    bool showGrid;
  46.    bool useAlphaGrid;
  47.    int gridOpacity;
  48. };
  49.  
  50. // Add after your existing structs
  51. struct CompressedStroke {
  52.    std::vector<DrawPoint> points;
  53.    COLORREF color;
  54.    int brushSize;
  55.    bool isEraser;
  56.    bool isCompressed;
  57. };
  58.  
  59. struct BatchRenderItem {
  60.    std::vector<DrawPoint> points;
  61.    COLORREF color;
  62.    int brushSize;
  63.    bool isEraser;
  64. };
  65.  
  66. std::mutex strokeMutex;
  67. std::vector<SerializedStroke> strokeHistory;
  68. bool isLoading = false;
  69. const double MAX_SPEED = 1000.0;
  70.  
  71. std::vector<DrawPoint> strokeBuffer;
  72. const int STROKE_BUFFER_SIZE = 3;
  73. const double MIN_DISTANCE = 2.0;
  74.  
  75. void CompressStroke(CompressedStroke& stroke, double tolerance = 2.0);
  76. void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize);
  77. void DrawBrush(HDC hdc, int x, int y, bool isEraser = false, bool interpolate = true);
  78. void Erase(HDC hdc, int x, int y);
  79. void ClearDrawing(HWND hwnd);
  80. void UpdateStatus(HWND hwnd);
  81. void RedrawAllStrokes();
  82. void ProcessBatch(HDC hdc);
  83. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  84.  
  85. int virtualWidth = 8192;
  86. int virtualHeight = 8192;
  87. int scrollX = 0;
  88. int scrollY = 0;
  89. bool isSpacePressed = false;
  90. bool isPanning = false;
  91. POINT lastDrawPoint = { -1, -1 };
  92. POINT lastMousePos = { 0, 0 };
  93. POINT dragStart = { 0, 0 };
  94. HDC hMemoryDC = NULL;
  95. HBITMAP hMemoryBitmap = NULL;
  96. HBITMAP hOldBitmap = NULL;
  97. HINSTANCE hInst;
  98. HDC hStatusBufferDC = NULL;
  99. HBITMAP hStatusBufferBitmap = NULL;
  100. POINT previousPoint;
  101. HDC hdc;
  102. HWND hWnd;
  103. DWORD lastStatusUpdateTime = 0;
  104. const DWORD STATUS_UPDATE_INTERVAL = 50;
  105. const int GRID_SIZE = 100;
  106. const COLORREF GRID_COLOR = RGB(255, 140, 0);
  107. HDC hGridDC = NULL;
  108. HBITMAP hGridBitmap = NULL;
  109. BOOL gridInitialized = FALSE;
  110. float gridZoomFactor = 1.0f;
  111. bool showGrid = true;
  112. bool useAlphaGrid = false;
  113. int gridOpacity = 255;
  114. COLORREF currentBrushColor = RGB(24, 123, 205);
  115. bool isInitialized = false;
  116. bool isPaintbrushSelected = true;
  117. bool isDrawing = false;
  118. bool isErasing = false;
  119. bool isClearing = false;
  120. bool isEraserMode = false;
  121. bool isEraserSelected = false;
  122. int minBrushSize = 5;
  123. int maxBrushSize = 50;
  124. int brushSize = 10;
  125. int loadProgress = 0;
  126. const wchar_t* STATE_FILE = L"canvas_state.bin";
  127. // Add these globals after your existing globals
  128. std::vector<BatchRenderItem> renderBatch;
  129. const size_t BATCH_SIZE = 100;
  130. HANDLE hMapFile = NULL;
  131. LPVOID pMapView = NULL;
  132. const size_t MAP_SIZE = 1024 * 1024 * 1024; // 1GB
  133. std::vector<CompressedStroke> compressedStrokes;
  134. size_t lastSavedStrokeIndex = 0;
  135.  
  136. void InitializeMemoryBitmap(HWND hwnd) {
  137.    HDC hdc = GetDC(hwnd);
  138.    if (!hdc) {
  139.        return;
  140.    }
  141.    hMemoryDC = CreateCompatibleDC(hdc);
  142.    if (!hMemoryDC) {
  143.        ReleaseDC(hwnd, hdc);
  144.        return;
  145.    }
  146.    hMemoryBitmap = CreateCompatibleBitmap(hdc, virtualWidth, virtualHeight);
  147.    if (!hMemoryBitmap) {
  148.        DeleteDC(hMemoryDC);
  149.        ReleaseDC(hwnd, hdc);
  150.        return;
  151.    }
  152.    hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hMemoryBitmap);
  153.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  154.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  155.    FillRect(hMemoryDC, &rect, whiteBrush);
  156.    DeleteObject(whiteBrush);
  157.    ReleaseDC(hwnd, hdc);
  158.    isInitialized = true;
  159.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  160.    SelectObject(hMemoryDC, guidePen);
  161.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  162.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  163.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  164.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  165.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  166.    SelectObject(hMemoryDC, edgePen);
  167.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  168.    DeleteObject(guidePen);
  169.    DeleteObject(edgePen);
  170.    RECT clientRect;
  171.    GetClientRect(hwnd, &clientRect);
  172.    scrollX = (virtualWidth - clientRect.right) / 2;
  173.    scrollY = (virtualHeight - clientRect.bottom) / 2;
  174. }
  175.  
  176.  
  177. void LoadCanvasStateAsync(HWND hwnd) {
  178.    std::thread([hwnd]() {
  179.        std::ifstream file(STATE_FILE, std::ios::binary | std::ios::in);
  180.        if (!file) {
  181.            isLoading = false;
  182.            return;
  183.        }
  184.  
  185.        try {
  186.            // Load canvas settings
  187.            file.read(reinterpret_cast<char*>(&gridZoomFactor), sizeof(float));
  188.            file.read(reinterpret_cast<char*>(&showGrid), sizeof(bool));
  189.            file.read(reinterpret_cast<char*>(&useAlphaGrid), sizeof(bool));
  190.            file.read(reinterpret_cast<char*>(&gridOpacity), sizeof(int));
  191.            file.read(reinterpret_cast<char*>(&currentBrushColor), sizeof(COLORREF));
  192.            file.read(reinterpret_cast<char*>(&brushSize), sizeof(int));
  193.  
  194.            size_t strokeCount;
  195.            file.read(reinterpret_cast<char*>(&strokeCount), sizeof(size_t));
  196.            std::vector<SerializedStroke> loadedStrokes;
  197.  
  198.            for (size_t i = 0; i < strokeCount && file.good(); ++i) {
  199.                SerializedStroke stroke;
  200.                size_t pointCount;
  201.                file.read(reinterpret_cast<char*>(&pointCount), sizeof(size_t));
  202.  
  203.                if (pointCount > 0 && pointCount < 1000000) {
  204.                    // Read points individually
  205.                    for (size_t j = 0; j < pointCount; ++j) {
  206.                        DrawPoint point;
  207.                        file.read(reinterpret_cast<char*>(&point.x), sizeof(int));
  208.                        file.read(reinterpret_cast<char*>(&point.y), sizeof(int));
  209.                        file.read(reinterpret_cast<char*>(&point.timestamp), sizeof(DWORD));
  210.                        stroke.points.push_back(point);
  211.                    }
  212.  
  213.                    file.read(reinterpret_cast<char*>(&stroke.color), sizeof(COLORREF));
  214.                    file.read(reinterpret_cast<char*>(&stroke.brushSize), sizeof(int));
  215.                    file.read(reinterpret_cast<char*>(&stroke.isEraser), sizeof(bool));
  216.                    loadedStrokes.push_back(stroke);
  217.                }
  218.            }
  219.  
  220.            {
  221.                std::lock_guard<std::mutex> lock(strokeMutex);
  222.                strokeHistory = std::move(loadedStrokes);
  223.            }
  224.  
  225.            RedrawAllStrokes();
  226.        }
  227.        catch (...) {
  228.            isLoading = false;
  229.            return;
  230.        }
  231.  
  232.        file.close();
  233.        isLoading = false;
  234.        InvalidateRect(hwnd, NULL, TRUE);
  235.        }).detach();
  236. }
  237.  
  238. void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize) {
  239.    if (points.empty()) return;
  240.  
  241.    COLORREF color = isEraser ? RGB(255, 255, 255) : strokeColor;
  242.    HBRUSH brush = CreateSolidBrush(color);
  243.    HPEN pen = CreatePen(PS_SOLID, 1, color);
  244.    HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
  245.    HPEN oldPen = (HPEN)SelectObject(hdc, pen);
  246.  
  247.    // Handle single point
  248.    if (points.size() == 1) {
  249.        const DrawPoint& pt = points[0];
  250.        Ellipse(hdc,
  251.            pt.x - strokeSize,
  252.            pt.y - strokeSize,
  253.            pt.x + strokeSize,
  254.            pt.y + strokeSize);
  255.    }
  256.    else {
  257.        // Original smooth stroke drawing code for multiple points
  258.        for (size_t i = 1; i < points.size(); ++i) {
  259.            const DrawPoint& prev = points[i - 1];
  260.            const DrawPoint& curr = points[i];
  261.            double dx = curr.x - prev.x;
  262.            double dy = curr.y - prev.y;
  263.            double distance = sqrt(dx * dx + dy * dy);
  264.            if (distance > 0) {
  265.                int steps = max(1, (int)(distance / 2));
  266.                for (int step = 0; step <= steps; ++step) {
  267.                    double t = step / (double)steps;
  268.                    int x = (int)(prev.x + dx * t);
  269.                    int y = (int)(prev.y + dy * t);
  270.                    Ellipse(hdc,
  271.                        x - strokeSize,
  272.                        y - strokeSize,
  273.                        x + strokeSize,
  274.                        y + strokeSize);
  275.                }
  276.            }
  277.        }
  278.    }
  279.  
  280.    SelectObject(hdc, oldBrush);
  281.    SelectObject(hdc, oldPen);
  282.    DeleteObject(brush);
  283.    DeleteObject(pen);
  284. }
  285.  
  286. void DrawBrush(HDC hdc, int x, int y, bool isEraser, bool interpolate) {
  287.    if (x < 0 || x > virtualWidth || y < 0 || y > virtualHeight) {
  288.        return;
  289.    }
  290.    COLORREF color = isEraser ? RGB(255, 255, 255) : currentBrushColor;
  291.    HBRUSH brush = CreateSolidBrush(color);
  292.    HPEN pen = CreatePen(PS_SOLID, 1, color);
  293.    HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
  294.    HPEN oldPen = (HPEN)SelectObject(hdc, pen);
  295.    Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
  296.    SelectObject(hdc, oldBrush);
  297.    SelectObject(hdc, oldPen);
  298.    DeleteObject(brush);
  299.    DeleteObject(pen);
  300. }
  301.  
  302. void StartStroke(int x, int y) {
  303.    strokeBuffer.clear();
  304.    strokeBuffer.push_back(DrawPoint(x, y));
  305. }
  306.  
  307. // Modify the AddToStroke function to ensure proper point spacing
  308. void AddToStroke(int x, int y) {
  309.    if (strokeBuffer.empty()) {
  310.        StartStroke(x, y);
  311.        return;
  312.    }
  313.  
  314.    const DrawPoint& lastPt = strokeBuffer.back();
  315.    double dx = x - lastPt.x;
  316.    double dy = y - lastPt.y;
  317.    double distance = sqrt(dx * dx + dy * dy);
  318.  
  319.    // Add intermediate points if the distance is too large
  320.    if (distance >= MIN_DISTANCE) {
  321.        int steps = (int)(distance / MIN_DISTANCE);
  322.        for (int i = 1; i <= steps; ++i) {
  323.            double t = i / (double)steps;
  324.            int interpX = (int)(lastPt.x + dx * t);
  325.            int interpY = (int)(lastPt.y + dy * t);
  326.            strokeBuffer.push_back(DrawPoint(interpX, interpY));
  327.        }
  328.    }
  329. }
  330.  
  331. void EndStroke() {
  332.    strokeBuffer.clear();
  333. }
  334.  
  335. void ShowColorPicker(HWND hwnd) {
  336.    CHOOSECOLOR cc = { sizeof(CHOOSECOLOR) };
  337.    static COLORREF customColors[16] = { 0 };
  338.    cc.hwndOwner = hwnd;
  339.    cc.rgbResult = currentBrushColor;
  340.    cc.lpCustColors = customColors;
  341.    cc.Flags = CC_FULLOPEN | CC_RGBINIT;
  342.    if (ChooseColor(&cc)) {
  343.        currentBrushColor = cc.rgbResult;
  344.        UpdateStatus(hwnd);
  345.    }
  346. }
  347.  
  348. void Erase(HDC hdc, int x, int y) {
  349.    HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
  350.    SelectObject(hdc, GetStockObject(NULL_PEN));
  351.    SelectObject(hdc, hBrush);
  352.    Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
  353.    DeleteObject(hBrush);
  354. }
  355.  
  356. void ClearCanvas(HWND hwnd) {
  357.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  358.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  359.    FillRect(hMemoryDC, &rect, whiteBrush);
  360.    DeleteObject(whiteBrush);
  361.    InvalidateRect(hwnd, NULL, FALSE);
  362. }
  363.  
  364. void ClearDrawing(HWND hwnd) {
  365.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  366.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  367.    FillRect(hMemoryDC, &rect, whiteBrush);
  368.    DeleteObject(whiteBrush);
  369.    RECT clientRect;
  370.    GetClientRect(hwnd, &clientRect);
  371.    scrollX = (virtualWidth - clientRect.right) / 2;
  372.    scrollY = (virtualHeight - clientRect.bottom) / 2;
  373.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  374.    SelectObject(hMemoryDC, guidePen);
  375.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  376.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  377.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  378.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  379.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  380.    SelectObject(hMemoryDC, edgePen);
  381.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  382.    DeleteObject(guidePen);
  383.    DeleteObject(edgePen);
  384.    InvalidateRect(hwnd, NULL, FALSE);
  385. }
  386.  
  387. void InitializeGridCache(HDC hdc) {
  388.    if (hGridDC) {
  389.        DeleteDC(hGridDC);
  390.        DeleteObject(hGridBitmap);
  391.    }
  392.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  393.    hGridDC = CreateCompatibleDC(hdc);
  394.    hGridBitmap = CreateCompatibleBitmap(hdc, scaledGridSize, scaledGridSize);
  395.    HBITMAP oldBitmap = (HBITMAP)SelectObject(hGridDC, hGridBitmap);
  396.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  397.    RECT rect = { 0, 0, scaledGridSize, scaledGridSize };
  398.    FillRect(hGridDC, &rect, whiteBrush);
  399.    DeleteObject(whiteBrush);
  400.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  401.    HPEN oldPen = (HPEN)SelectObject(hGridDC, gridPen);
  402.    MoveToEx(hGridDC, scaledGridSize - 1, 0, NULL);
  403.    LineTo(hGridDC, scaledGridSize - 1, scaledGridSize);
  404.    MoveToEx(hGridDC, 0, scaledGridSize - 1, NULL);
  405.    LineTo(hGridDC, scaledGridSize, scaledGridSize - 1);
  406.    SelectObject(hGridDC, oldPen);
  407.    DeleteObject(gridPen);
  408.    gridInitialized = TRUE;
  409. }
  410.  
  411. void DrawGrid(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
  412.    SetBkMode(hdc, TRANSPARENT);
  413.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  414.    HPEN oldPen = (HPEN)SelectObject(hdc, gridPen);
  415.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  416.    int offsetX = -(scrollX % scaledGridSize);
  417.    int offsetY = -(scrollY % scaledGridSize);
  418.    for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
  419.        MoveToEx(hdc, x, 0, NULL);
  420.        LineTo(hdc, x, clientRect.bottom);
  421.    }
  422.    for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
  423.        MoveToEx(hdc, 0, y, NULL);
  424.        LineTo(hdc, clientRect.right, y);
  425.    }
  426.    SelectObject(hdc, oldPen);
  427.    DeleteObject(gridPen);
  428. }
  429.  
  430. void DrawGridWithAlpha(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
  431.    HDC tempDC = CreateCompatibleDC(hdc);
  432.    HBITMAP tempBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
  433.    HBITMAP oldTempBitmap = (HBITMAP)SelectObject(tempDC, tempBitmap);
  434.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  435.    FillRect(tempDC, &clientRect, whiteBrush);
  436.    DeleteObject(whiteBrush);
  437.    SetBkMode(tempDC, TRANSPARENT);
  438.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  439.    HPEN oldPen = (HPEN)SelectObject(tempDC, gridPen);
  440.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  441.    int offsetX = -(scrollX % scaledGridSize);
  442.    int offsetY = -(scrollY % scaledGridSize);
  443.    for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
  444.        MoveToEx(tempDC, x, 0, NULL);
  445.        LineTo(tempDC, x, clientRect.bottom);
  446.    }
  447.    for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
  448.        MoveToEx(tempDC, 0, y, NULL);
  449.        LineTo(tempDC, clientRect.right, y);
  450.    }
  451.    SelectObject(tempDC, oldPen);
  452.    DeleteObject(gridPen);
  453.    BLENDFUNCTION bf;
  454.    bf.BlendOp = AC_SRC_OVER;
  455.    bf.BlendFlags = 0;
  456.    bf.SourceConstantAlpha = gridOpacity;
  457.    bf.AlphaFormat = 0;
  458.    AlphaBlend(hdc, 0, 0, clientRect.right, clientRect.bottom,
  459.        tempDC, 0, 0, clientRect.right, clientRect.bottom,
  460.        bf);
  461.    SelectObject(tempDC, oldTempBitmap);
  462.    DeleteObject(tempBitmap);
  463.    DeleteDC(tempDC);
  464. }
  465.  
  466. void InitializeStatusBuffer(HWND hStatus) {
  467.    if (hStatusBufferDC) {
  468.        DeleteDC(hStatusBufferDC);
  469.        DeleteObject(hStatusBufferBitmap);
  470.    }
  471.    HDC hdc = GetDC(hStatus);
  472.    RECT rect;
  473.    GetClientRect(hStatus, &rect);
  474.    hStatusBufferDC = CreateCompatibleDC(hdc);
  475.    hStatusBufferBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
  476.    SelectObject(hStatusBufferDC, hStatusBufferBitmap);
  477.    ReleaseDC(hStatus, hdc);
  478. }
  479.  
  480. void UpdateStatus(HWND hwnd) {
  481.    DWORD currentTime = GetTickCount();
  482.    if (currentTime - lastStatusUpdateTime < STATUS_UPDATE_INTERVAL) {
  483.        return;
  484.    }
  485.    lastStatusUpdateTime = currentTime;
  486.    HWND hStatus = GetDlgItem(hwnd, 0);
  487.    if (!hStatus) return;
  488.    if (!hStatusBufferDC) {
  489.        InitializeStatusBuffer(hStatus);
  490.    }
  491.    RECT statusRect;
  492.    GetClientRect(hStatus, &statusRect);
  493.    SendMessage(hStatus, WM_ERASEBKGND, (WPARAM)hStatusBufferDC, 0);
  494.    wchar_t status[512];
  495.    BYTE r = GetRValue(currentBrushColor);
  496.    BYTE g = GetGValue(currentBrushColor);
  497.    BYTE b = GetBValue(currentBrushColor);
  498.    int centerX = scrollX + (virtualWidth / 2);
  499.    int centerY = scrollY + (virtualHeight / 2);
  500.    swprintf_s(status,
  501.        L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s%s | Zoom: %.1fx | Opacity: %d%% | Canvas Pos: (%d,%d)",
  502.        isEraserMode ? L"Eraser" : L"Draw",
  503.        brushSize,
  504.        r, g, b,
  505.        showGrid ? L"On" : L"Off",
  506.        useAlphaGrid ? L"(Alpha)" : L"",
  507.        gridZoomFactor,
  508.        (gridOpacity * 100) / 255,
  509.        centerX, centerY
  510.    );
  511.    HDC hdc = GetDC(hStatus);
  512.    SetBkMode(hStatusBufferDC, TRANSPARENT);
  513.    SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)status);
  514.    BitBlt(hdc, 0, 0, statusRect.right, statusRect.bottom,
  515.        hStatusBufferDC, 0, 0, SRCCOPY);
  516.    ReleaseDC(hStatus, hdc);
  517. }
  518.  
  519. void RedrawAllStrokes() {
  520.    if (!hMemoryDC) return;
  521.  
  522.    // Clear the canvas with white background
  523.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  524.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  525.    FillRect(hMemoryDC, &rect, whiteBrush);
  526.    DeleteObject(whiteBrush);
  527.  
  528.    // Draw guide lines
  529.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  530.    SelectObject(hMemoryDC, guidePen);
  531.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  532.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  533.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  534.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  535.    DeleteObject(guidePen);
  536.  
  537.    // Draw canvas border
  538.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  539.    SelectObject(hMemoryDC, edgePen);
  540.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  541.    DeleteObject(edgePen);
  542.  
  543.    // Draw all strokes
  544.    std::lock_guard<std::mutex> lock(strokeMutex);
  545.    for (const auto& stroke : strokeHistory) {
  546.        if (stroke.points.empty()) continue;  // Skip empty strokes
  547.  
  548.        // Handle single-point strokes
  549.        if (stroke.points.size() == 1) {
  550.            const DrawPoint& pt = stroke.points[0];
  551.            COLORREF color = stroke.isEraser ? RGB(255, 255, 255) : stroke.color;
  552.            HBRUSH brush = CreateSolidBrush(color);
  553.            HPEN pen = CreatePen(PS_SOLID, 1, color);
  554.            HBRUSH oldBrush = (HBRUSH)SelectObject(hMemoryDC, brush);
  555.            HPEN oldPen = (HPEN)SelectObject(hMemoryDC, pen);
  556.  
  557.            Ellipse(hMemoryDC,
  558.                pt.x - stroke.brushSize,
  559.                pt.y - stroke.brushSize,
  560.                pt.x + stroke.brushSize,
  561.                pt.y + stroke.brushSize);
  562.  
  563.            SelectObject(hMemoryDC, oldBrush);
  564.            SelectObject(hMemoryDC, oldPen);
  565.            DeleteObject(brush);
  566.            DeleteObject(pen);
  567.        }
  568.        // Handle multi-point strokes
  569.        else {
  570.            DrawSmoothStroke(hMemoryDC, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize);
  571.        }
  572.    }
  573. }
  574.  
  575. void CompressStroke(CompressedStroke& stroke, double tolerance) {
  576.    if (stroke.points.size() < 3) return;
  577.  
  578.    std::vector<DrawPoint> compressed;
  579.    compressed.push_back(stroke.points.front());
  580.  
  581.    for (size_t i = 1; i < stroke.points.size() - 1; ++i) {
  582.        const DrawPoint& prev = stroke.points[i - 1];
  583.        const DrawPoint& curr = stroke.points[i];
  584.        const DrawPoint& next = stroke.points[i + 1];
  585.  
  586.        double dx = next.x - prev.x;
  587.        double dy = next.y - prev.y;
  588.  
  589.        double length = sqrt(dx * dx + dy * dy);
  590.        if (length > tolerance) {
  591.            compressed.push_back(curr);
  592.        }
  593.    }
  594.  
  595.    compressed.push_back(stroke.points.back());
  596.    stroke.points = std::move(compressed);
  597.    stroke.isCompressed = true;
  598. }
  599.  
  600. void SaveCanvasState() {
  601.    std::ofstream file(STATE_FILE, std::ios::binary | std::ios::out);
  602.    if (!file) return;
  603.  
  604.    file.write(reinterpret_cast<const char*>(&gridZoomFactor), sizeof(float));
  605.    file.write(reinterpret_cast<const char*>(&showGrid), sizeof(bool));
  606.    file.write(reinterpret_cast<const char*>(&useAlphaGrid), sizeof(bool));
  607.    file.write(reinterpret_cast<const char*>(&gridOpacity), sizeof(int));
  608.    file.write(reinterpret_cast<const char*>(&currentBrushColor), sizeof(COLORREF));
  609.    file.write(reinterpret_cast<const char*>(&brushSize), sizeof(int));
  610.  
  611.    std::lock_guard<std::mutex> lock(strokeMutex);
  612.    size_t strokeCount = strokeHistory.size();
  613.    file.write(reinterpret_cast<const char*>(&strokeCount), sizeof(size_t));
  614.  
  615.    for (const auto& stroke : strokeHistory) {
  616.        // Ensure minimum point spacing
  617.        std::vector<DrawPoint> optimizedPoints;
  618.        if (!stroke.points.empty()) {
  619.            optimizedPoints.push_back(stroke.points[0]);
  620.            for (size_t i = 1; i < stroke.points.size(); ++i) {
  621.                const DrawPoint& prev = optimizedPoints.back();
  622.                const DrawPoint& curr = stroke.points[i];
  623.                double dx = curr.x - prev.x;
  624.                double dy = curr.y - prev.y;
  625.                double distance = sqrt(dx * dx + dy * dy);
  626.  
  627.                if (distance >= MIN_DISTANCE) {
  628.                    optimizedPoints.push_back(curr);
  629.                }
  630.            }
  631.        }
  632.  
  633.        size_t pointCount = optimizedPoints.size();
  634.        file.write(reinterpret_cast<const char*>(&pointCount), sizeof(size_t));
  635.        file.write(reinterpret_cast<const char*>(optimizedPoints.data()), pointCount * sizeof(DrawPoint));
  636.        file.write(reinterpret_cast<const char*>(&stroke.color), sizeof(COLORREF));
  637.        file.write(reinterpret_cast<const char*>(&stroke.brushSize), sizeof(int));
  638.        file.write(reinterpret_cast<const char*>(&stroke.isEraser), sizeof(bool));
  639.    }
  640.    file.close();
  641. }
  642.  
  643. void ProcessBatch(HDC hdc) {
  644.    if (renderBatch.empty()) return;
  645.  
  646.    for (const auto& item : renderBatch) {
  647.        DrawSmoothStroke(hdc, item.points, item.isEraser, item.color, item.brushSize);
  648.    }
  649.    renderBatch.clear();
  650. }
  651.  
  652. void AddToBatch(const std::vector<DrawPoint>& points, COLORREF color, int brushSize, bool isEraser) {
  653.    BatchRenderItem item;
  654.    item.points = points;
  655.    item.color = color;
  656.    item.brushSize = brushSize;
  657.    item.isEraser = isEraser;
  658.  
  659.    renderBatch.push_back(item);
  660.  
  661.    if (renderBatch.size() >= BATCH_SIZE) {
  662.        ProcessBatch(hMemoryDC);
  663.    }
  664. }
  665.  
  666. bool InitializeMemoryMappedFile() {
  667.    hMapFile = CreateFileMapping(
  668.        INVALID_HANDLE_VALUE,
  669.        NULL,
  670.        PAGE_READWRITE,
  671.        0,
  672.        MAP_SIZE,
  673.        L"InfiniteCanvasMapping"
  674.    );
  675.  
  676.    if (hMapFile == NULL) return false;
  677.  
  678.    pMapView = MapViewOfFile(
  679.        hMapFile,
  680.        FILE_MAP_ALL_ACCESS,
  681.        0,
  682.        0,
  683.        MAP_SIZE
  684.    );
  685.  
  686.    return (pMapView != NULL);
  687. }
  688.  
  689. void CleanupMemoryMappedFile() {
  690.    if (pMapView) {
  691.        UnmapViewOfFile(pMapView);
  692.        pMapView = NULL;
  693.    }
  694.    if (hMapFile) {
  695.        CloseHandle(hMapFile);
  696.        hMapFile = NULL;
  697.    }
  698. }
  699.  
  700. void RenderStrokeSegment(HDC targetDC, const std::vector<DrawPoint>& points, bool isEraser, COLORREF color, int size, int offsetX = 0, int offsetY = 0) {
  701.    if (points.size() < 2) return;
  702.  
  703.    std::vector<DrawPoint> adjustedPoints;
  704.    for (const auto& pt : points) {
  705.        adjustedPoints.push_back(DrawPoint(pt.x - offsetX, pt.y - offsetY));
  706.    }
  707.  
  708.    DrawSmoothStroke(targetDC, adjustedPoints, isEraser, color, size);
  709. }
  710.  
  711. // Add these helper functions before your WindowProc
  712. inline int MinInt(int a, int b) { return (a < b) ? a : b; }
  713. inline int MaxInt(int a, int b) { return (a > b) ? a : b; }
  714.  
  715. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  716.    switch (uMsg) {
  717.    case WM_CREATE:
  718.    {
  719.        if (!InitializeMemoryMappedFile()) {
  720.            MessageBox(hwnd, L"Failed to initialize memory mapping", L"Error", MB_OK);
  721.            return -1;
  722.        }
  723.  
  724.        InitializeMemoryBitmap(hwnd);
  725.        if (hMemoryDC) {
  726.            HBRUSH redBrush = CreateSolidBrush(RGB(255, 0, 0));
  727.            RECT testRect = { 100, 100, 200, 200 };
  728.            FillRect(hMemoryDC, &testRect, redBrush);
  729.            DeleteObject(redBrush);
  730.            InvalidateRect(hwnd, NULL, FALSE);
  731.        }
  732.        else {
  733.            MessageBox(hwnd, L"Memory DC failed to initialize", L"Error", MB_OK);
  734.        }
  735.  
  736.        // Create status bar
  737.        HWND hStatus = CreateWindowEx(
  738.            0,
  739.            STATUSCLASSNAME,
  740.            NULL,
  741.            WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
  742.            0, 0, 0, 0,
  743.            hwnd,
  744.            (HMENU)0,
  745.            hInst,
  746.            NULL
  747.        );
  748.  
  749.        if (hStatus) {
  750.            int statwidths[] = { -1 };
  751.            SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)statwidths);
  752.            UpdateStatus(hwnd);
  753.        }
  754.  
  755.        isLoading = true;
  756.        LoadCanvasStateAsync(hwnd);
  757.        return 0;
  758.    }
  759.    case WM_SYSCOMMAND:
  760.    {
  761.        if (wParam == SC_CLOSE) {
  762.            DestroyWindow(hwnd);
  763.            return 0;
  764.        }
  765.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  766.    }
  767.    case WM_KEYDOWN:
  768.    {
  769.        if (GetKeyState(VK_MENU) & 0x8000) {
  770.            return DefWindowProc(hwnd, uMsg, wParam, lParam);
  771.        }
  772.        if (wParam == VK_SPACE && !isSpacePressed) {
  773.            isSpacePressed = true;
  774.            GetCursorPos(&lastMousePos);
  775.            ScreenToClient(hwnd, &lastMousePos);
  776.            SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  777.            SetCapture(hwnd);
  778.            return 0;
  779.        }
  780.        else if (wParam == 0x50) {
  781.            isPaintbrushSelected = true;
  782.            isEraserMode = false;
  783.            UpdateStatus(hwnd);
  784.        }
  785.        else if (wParam == 0x45) {
  786.            isPaintbrushSelected = false;
  787.            isEraserMode = true;
  788.            UpdateStatus(hwnd);
  789.        }
  790.        else if (wParam == 'Q') {
  791.            ShowColorPicker(hwnd);
  792.        }
  793.        else if (wParam == VK_ADD || wParam == VK_OEM_PLUS) {
  794.            brushSize = std::min(50, brushSize + 5);
  795.            UpdateStatus(hwnd);
  796.        }
  797.        else if (wParam == VK_SUBTRACT || wParam == VK_OEM_MINUS) {
  798.            brushSize = std::max(5, brushSize - 5);
  799.            UpdateStatus(hwnd);
  800.        }
  801.        else if (wParam == 0x43) {
  802.            if (!(GetKeyState(VK_CONTROL) & 0x8000)) {
  803.                ClearDrawing(hwnd);
  804.            }
  805.        }
  806.        else if (wParam == VK_HOME) {
  807.            RECT clientRect;
  808.            GetClientRect(hwnd, &clientRect);
  809.            scrollX = (virtualWidth - clientRect.right) / 2;
  810.            scrollY = (virtualHeight - clientRect.bottom) / 2;
  811.            InvalidateRect(hwnd, NULL, FALSE);
  812.        }
  813.        else if (wParam == 'G') {
  814.            showGrid = !showGrid;
  815.            InvalidateRect(hwnd, NULL, FALSE);
  816.            UpdateStatus(hwnd);
  817.        }
  818.        else if (wParam == 'A') {
  819.            useAlphaGrid = !useAlphaGrid;
  820.            InvalidateRect(hwnd, NULL, FALSE);
  821.            UpdateStatus(hwnd);
  822.        }
  823.        if (wParam == VK_PRIOR) {
  824.            gridZoomFactor *= 1.1f;
  825.            gridInitialized = FALSE;
  826.            InvalidateRect(hwnd, NULL, FALSE);
  827.            UpdateStatus(hwnd);
  828.        }
  829.        else if (wParam == VK_NEXT) {
  830.            gridZoomFactor *= 0.9f;
  831.            if (gridZoomFactor < 0.1f) gridZoomFactor = 0.1f;
  832.            gridInitialized = FALSE;
  833.            InvalidateRect(hwnd, NULL, FALSE);
  834.            UpdateStatus(hwnd);
  835.        }
  836.        else if (wParam == VK_OEM_6 && useAlphaGrid) {
  837.            gridOpacity = min(255, gridOpacity + 15);
  838.            InvalidateRect(hwnd, NULL, FALSE);
  839.            UpdateStatus(hwnd);
  840.        }
  841.        else if (wParam == VK_OEM_4 && useAlphaGrid) {
  842.            gridOpacity = max(0, gridOpacity - 15);
  843.            InvalidateRect(hwnd, NULL, FALSE);
  844.            UpdateStatus(hwnd);
  845.        }
  846.        else if (wParam == VK_ESCAPE) {
  847.            ProcessBatch(hMemoryDC); // Process any remaining batched strokes
  848.            CleanupMemoryMappedFile();
  849.            //if (!strokeBuffer.empty()) {  // Only save if there are points
  850.                //SaveCanvasState();  // Save canvas state before exiting
  851.            //}
  852.            PostQuitMessage(0);
  853.            return 0;
  854.        }
  855.        else if (wParam == VK_F1) {
  856.            MessageBox(hwnd, TEXT("I made an Infinite Canvas app using GDI and Memory DC, no need for bloated Godot Engine/ Frameworks or M$ Infinite Canvas Control! Eternity of effort paid off! (1383 lines of code) by Entisoft Software (c) Evans Thorpemorton"), TEXT("Information"), MB_OK | MB_ICONINFORMATION);
  857.        }
  858.        return 0;
  859.    }
  860.    case WM_KEYUP:
  861.    {
  862.        if (wParam == VK_SPACE) {
  863.            isSpacePressed = false;
  864.            SetCursor(LoadCursor(NULL, IDC_ARROW));
  865.            ReleaseCapture();
  866.            return 0;
  867.        }
  868.    }
  869.    case WM_LBUTTONDOWN:
  870.    {
  871.        if (!isSpacePressed) {
  872.            isDrawing = true;
  873.            SetCapture(hwnd);
  874.            // Extract x and y from lParam
  875.            int x = GET_X_LPARAM(lParam);
  876.            int y = GET_Y_LPARAM(lParam);
  877.            int canvasX = x + scrollX;
  878.            int canvasY = y + scrollY;
  879.            if (canvasX >= 0 && canvasX <= virtualWidth &&
  880.                canvasY >= 0 && canvasY <= virtualHeight) {
  881.  
  882.                StartStroke(canvasX, canvasY);
  883.  
  884.                // Draw initial point
  885.                DrawBrush(hMemoryDC, canvasX, canvasY, isEraserMode, false);
  886.  
  887.                // Draw to screen
  888.                HDC screenDC = GetDC(hwnd);
  889.                if (screenDC) {
  890.                    DrawBrush(screenDC, x, y, isEraserMode, false);
  891.                    ReleaseDC(hwnd, screenDC);
  892.                }
  893.  
  894.                InvalidateRect(hwnd, NULL, FALSE);
  895.            }
  896.        }
  897.        return 0;
  898.    }
  899.    // In WM_LBUTTONUP case:
  900.    case WM_LBUTTONUP:
  901.    {
  902.        if (isDrawing) {
  903.            isDrawing = false;
  904.            SerializedStroke stroke;
  905.            stroke.points = strokeBuffer;  // Even if it's just one point
  906.             stroke.color = currentBrushColor;
  907.             stroke.brushSize = brushSize;
  908.             stroke.isEraser = isEraserMode;
  909.             {
  910.                 std::lock_guard<std::mutex> lock(strokeMutex);
  911.                 strokeHistory.push_back(stroke);
  912.             }
  913.             SaveCanvasState();
  914.             EndStroke();
  915.             ReleaseCapture();
  916.             InvalidateRect(hwnd, NULL, FALSE);
  917.         }
  918.         return 0;
  919.     }
  920.     case WM_MOUSEMOVE:
  921.     {
  922.         int x = LOWORD(lParam);
  923.         int y = HIWORD(lParam);
  924.  
  925.         if (isSpacePressed) {
  926.             RECT clientRect;
  927.             GetClientRect(hwnd, &clientRect);
  928.             int deltaX = x - lastMousePos.x;
  929.             int deltaY = y - lastMousePos.y;
  930.  
  931.             // Calculate new scroll positions with bounds checking
  932.             int newScrollX = scrollX - deltaX;
  933.             int newScrollY = scrollY - deltaY;
  934.  
  935.             // Clamp scroll values using std::max and std::min
  936.             //newScrollX = std::max(0, std::min(virtualWidth - clientRect.right, newScrollX));
  937.             newScrollX = MaxInt(0, MinInt(virtualWidth - clientRect.right, newScrollX));
  938.             //newScrollY = std::max(0, std::min(virtualHeight - clientRect.bottom, newScrollY));
  939.             newScrollY = MaxInt(0, MinInt(virtualHeight - clientRect.bottom, newScrollY));
  940.  
  941.             if (newScrollX != scrollX || newScrollY != scrollY) {
  942.                 scrollX = newScrollX;
  943.                 scrollY = newScrollY;
  944.                 ScrollWindowEx(hwnd, deltaX, deltaY,
  945.                     NULL, NULL, NULL, NULL,
  946.                     SW_INVALIDATE | SW_ERASE);
  947.  
  948.                 // Update status less frequently during panning
  949.                 if (std::abs(deltaX) > 5 || std::abs(deltaY) > 5) {
  950.                     UpdateStatus(hwnd);
  951.                 }
  952.             }
  953.             lastMousePos.x = x;
  954.             lastMousePos.y = y;
  955.         }
  956.         else if (isDrawing && (wParam & MK_LBUTTON)) {
  957.             int canvasX = x + scrollX;
  958.             int canvasY = y + scrollY;
  959.             if (canvasX >= 0 && canvasX <= virtualWidth &&
  960.                 canvasY >= 0 && canvasY <= virtualHeight) {
  961.  
  962.                 AddToStroke(canvasX, canvasY); // Use the stroke buffer system
  963.  
  964.                 // Draw the current segment to both memory DC and screen
  965.                 if (strokeBuffer.size() >= 2) {
  966.                     std::vector<DrawPoint> currentSegment;
  967.                     currentSegment.push_back(strokeBuffer[strokeBuffer.size() - 2]);
  968.                     currentSegment.push_back(strokeBuffer[strokeBuffer.size() - 1]);
  969.  
  970.                     // Draw to memory DC (permanent canvas)
  971.                     DrawSmoothStroke(hMemoryDC, currentSegment, isEraserMode, currentBrushColor, brushSize);
  972.  
  973.                     // Draw to screen (temporary view)
  974.                     HDC screenDC = GetDC(hwnd);
  975.                     if (screenDC) {
  976.                         std::vector<DrawPoint> screenPoints;
  977.                         for (const auto& pt : currentSegment) {
  978.                             screenPoints.push_back(DrawPoint(
  979.                                 pt.x - scrollX,
  980.                                 pt.y - scrollY
  981.                             ));
  982.                         }
  983.                         DrawSmoothStroke(screenDC, screenPoints, isEraserMode, currentBrushColor, brushSize);
  984.                         ReleaseDC(hwnd, screenDC);
  985.                     }
  986.                 }
  987.  
  988.                 DWORD currentTime = GetTickCount();
  989.                 if (currentTime - lastStatusUpdateTime > STATUS_UPDATE_INTERVAL) {
  990.                     UpdateStatus(hwnd);
  991.                     lastStatusUpdateTime = currentTime;
  992.                 }
  993.             }
  994.         }
  995.         return 0;
  996.     }
  997.     case WM_SIZE:
  998.     {
  999.         HWND hStatus = GetDlgItem(hwnd, 0);
  1000.         if (hStatus) {
  1001.             SendMessage(hStatus, WM_SIZE, 0, 0);
  1002.             InitializeStatusBuffer(hStatus);
  1003.             UpdateStatus(hwnd);
  1004.         }
  1005.         if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {
  1006.             InvalidateRect(hwnd, NULL, TRUE);
  1007.         }
  1008.         return 0;
  1009.     }
  1010.     case WM_PAINT:
  1011.     {
  1012.         PAINTSTRUCT ps;
  1013.         HDC hdc = BeginPaint(hwnd, &ps);
  1014.         RECT clientRect;
  1015.         GetClientRect(hwnd, &clientRect);
  1016.         int windowWidth = clientRect.right - clientRect.left;
  1017.         int windowHeight = clientRect.bottom - clientRect.top;
  1018.  
  1019.         HDC memDC = CreateCompatibleDC(hdc);
  1020.         HBITMAP memBitmap = CreateCompatibleBitmap(hdc, windowWidth, windowHeight);
  1021.         HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
  1022.  
  1023.         HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  1024.         FillRect(memDC, &clientRect, whiteBrush);
  1025.         DeleteObject(whiteBrush);
  1026.  
  1027.         BitBlt(memDC, 0, 0, windowWidth, windowHeight, hMemoryDC, scrollX, scrollY, SRCCOPY);
  1028.  
  1029.         if (showGrid) {
  1030.             if (useAlphaGrid) {
  1031.                 DrawGridWithAlpha(memDC, clientRect, scrollX, scrollY);
  1032.             }
  1033.             else {
  1034.                 DrawGrid(memDC, clientRect, scrollX, scrollY);
  1035.             }
  1036.         }
  1037.  
  1038.         BitBlt(hdc, 0, 0, windowWidth, windowHeight, memDC, 0, 0, SRCCOPY);
  1039.  
  1040.         SelectObject(memDC, oldBitmap);
  1041.         DeleteObject(memBitmap);
  1042.         DeleteDC(memDC);
  1043.  
  1044.         EndPaint(hwnd, &ps);
  1045.         return 0;
  1046.     }
  1047.     case WM_SETCURSOR:
  1048.     {
  1049.         if (LOWORD(lParam) == HTCLIENT) {
  1050.             if (isSpacePressed) {
  1051.                 SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  1052.                 return TRUE;
  1053.             }
  1054.             else if (isPaintbrushSelected || isEraserMode) {
  1055.                 SetCursor(LoadCursor(NULL, IDC_CROSS));
  1056.                 return TRUE;
  1057.             }
  1058.         }
  1059.         return DefWindowProc(hwnd, uMsg, wParam, lParam);
  1060.     }
  1061.     case WM_MOUSEWHEEL:
  1062.     {
  1063.         int delta = GET_WHEEL_DELTA_WPARAM(wParam);
  1064.         if (GetKeyState(VK_CONTROL) & 0x8000) {
  1065.             if (delta > 0)
  1066.                 gridZoomFactor *= 1.1f;
  1067.             else
  1068.                 gridZoomFactor *= 0.9f;
  1069.             InvalidateRect(hwnd, NULL, FALSE);
  1070.         }
  1071.         return 0;
  1072.     }
  1073.     case WM_DESTROY:
  1074.     {
  1075.         ProcessBatch(hMemoryDC); // Process any remaining batched strokes
  1076.         CleanupMemoryMappedFile();
  1077.         // Save the canvas state before exiting
  1078.         //if (!strokeHistory.empty()) {
  1079.             //SaveCanvasState();
  1080.         //}
  1081.  
  1082.         // Clean up resources
  1083.         if (hGridDC) {
  1084.             DeleteDC(hGridDC);
  1085.             hGridDC = NULL;
  1086.         }
  1087.         if (hGridBitmap) {
  1088.             DeleteObject(hGridBitmap);
  1089.             hGridBitmap = NULL;
  1090.         }
  1091.         if (hMemoryDC) {
  1092.             if (hOldBitmap) {
  1093.                 SelectObject(hMemoryDC, hOldBitmap);
  1094.                 hOldBitmap = NULL;
  1095.             }
  1096.             DeleteDC(hMemoryDC);
  1097.             hMemoryDC = NULL;
  1098.         }
  1099.         if (hMemoryBitmap) {
  1100.             DeleteObject(hMemoryBitmap);
  1101.             hMemoryBitmap = NULL;
  1102.         }
  1103.         if (hStatusBufferDC) {
  1104.             DeleteDC(hStatusBufferDC);
  1105.             hStatusBufferDC = NULL;
  1106.         }
  1107.         if (hStatusBufferBitmap) {
  1108.             DeleteObject(hStatusBufferBitmap);
  1109.             hStatusBufferBitmap = NULL;
  1110.         }
  1111.  
  1112.         PostQuitMessage(0);
  1113.         return 0;
  1114.     }
  1115.     default:
  1116.         return DefWindowProc(hwnd, uMsg, wParam, lParam);
  1117.     }
  1118.     return 0;
  1119. }
  1120.  
  1121. INITCOMMONCONTROLSEX icex;
  1122.  
  1123. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
  1124.     icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
  1125.     icex.dwICC = ICC_BAR_CLASSES;
  1126.     InitCommonControlsEx(&icex);
  1127.  
  1128.     const wchar_t CLASS_NAME[] = L"DoodleAppClass";
  1129.  
  1130.     WNDCLASS wc = { };
  1131.     wc.lpfnWndProc = WindowProc;
  1132.     wc.hInstance = hInstance;
  1133.     wc.lpszClassName = CLASS_NAME;
  1134.     wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
  1135.  
  1136.     RegisterClass(&wc);
  1137.  
  1138.     hInst = hInstance;
  1139.  
  1140.     HWND hwnd = CreateWindowEx(
  1141.         0,
  1142.         CLASS_NAME,
  1143.         L"Infinite Canvas Doodle App (P=Brush E=Eraser C=Clear +-=BrushSize Space+Drag=Scroll Home=Center Q=Color G=Grid A=Alpha""[]"" PgUp = ZoomIn PgDown = ZoomOut F1 = About)",
  1144.         WS_OVERLAPPEDWINDOW | WS_MAXIMIZE,
  1145.         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  1146.         NULL,
  1147.         NULL,
  1148.         hInstance,
  1149.         NULL
  1150.     );
  1151.  
  1152.     SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_COMPOSITED);
  1153.  
  1154.     if (hwnd == NULL) {
  1155.         return 0;
  1156.     }
  1157.  
  1158.     HWND hStatus = CreateWindowEx(
  1159.         0,
  1160.         STATUSCLASSNAME,
  1161.         NULL,
  1162.         WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
  1163.         0, 0, 0, 0,
  1164.         hwnd,
  1165.         (HMENU)0,
  1166.         hInstance,
  1167.         NULL
  1168.     );
  1169.  
  1170.     if (hStatus) {
  1171.         int statwidths[] = { -1 };
  1172.         SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)statwidths);
  1173.         UpdateStatus(hwnd);
  1174.     }
  1175.  
  1176.     ShowWindow(hwnd, SW_SHOWMAXIMIZED);
  1177.  
  1178.     MSG msg = {};
  1179.     while (GetMessage(&msg, NULL, 0, 0)) {
  1180.         TranslateMessage(&msg);
  1181.         DispatchMessage(&msg);
  1182.     }
  1183.  
  1184.     return 0;
  1185. }
Add Comment
Please, Sign In to add comment