alien_fx_fiend

Infinite Canvas Doodling /w Session Resume !! (Color,Size,Eraser Serialized) *FINAL RELEASE*

Nov 29th, 2024 (edited)
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 32.86 KB | Source Code | 0 0
  1. ==++ Here's the full source code for (file 1/1) "main.cpp":: ++==
  2. #define NOMINMAX
  3. #define WIN32_LEAN_AND_MEAN
  4. #include <windows.h>
  5. #include <windef.h>
  6. #include <wingdi.h>
  7. #include <algorithm>
  8. #include <iostream>
  9. #include <vector>
  10. #include <cmath>
  11. #include <commctrl.h>
  12. #include <commdlg.h>  
  13. #include <stdio.h>
  14. #include <fstream>
  15. #include <thread>
  16. #include <mutex>
  17. #include "resource.h"  
  18.  
  19. #pragma comment(lib, "comctl32.lib")
  20. #pragma comment(lib, "comdlg32.lib")
  21. #pragma comment(lib, "msimg32.lib")
  22.  
  23. using namespace std;
  24.  
  25. struct DrawPoint {
  26.    int x, y;
  27.    DWORD timestamp;
  28.    DrawPoint() : x(0), y(0), timestamp(0) {}
  29.    DrawPoint(int px, int py) : x(px), y(py), timestamp(GetTickCount()) {}
  30. };
  31.  
  32. struct SerializedStroke {
  33.    std::vector<DrawPoint> points;
  34.    COLORREF color;
  35.    int brushSize;
  36.    bool isEraser;
  37. };
  38.  
  39. struct CanvasState {
  40.    std::vector<SerializedStroke> strokes;
  41.    float gridZoomFactor;
  42.    bool showGrid;
  43.    bool useAlphaGrid;
  44.    int gridOpacity;
  45. };
  46.  
  47. std::mutex strokeMutex;
  48. std::vector<SerializedStroke> strokeHistory;
  49. bool isLoading = false;
  50. const double MAX_SPEED = 1000.0;
  51.  
  52. std::vector<DrawPoint> strokeBuffer;
  53. const int STROKE_BUFFER_SIZE = 3;
  54. const double MIN_DISTANCE = 2.0;
  55.  
  56. void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor);
  57. void DrawBrush(HDC hdc, int x, int y, bool isEraser = false, bool interpolate = true);
  58. void Erase(HDC hdc, int x, int y);
  59. void ClearDrawing(HWND hwnd);
  60. void UpdateStatus(HWND hwnd);
  61. void RedrawAllStrokes();
  62. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  63.  
  64. int virtualWidth = 8192;
  65. int virtualHeight = 8192;
  66. int scrollX = 0;
  67. int scrollY = 0;
  68. bool isSpacePressed = false;
  69. bool isPanning = false;
  70. POINT lastDrawPoint = { -1, -1 };
  71. POINT lastMousePos = { 0, 0 };
  72. POINT dragStart = { 0, 0 };
  73. HDC hMemoryDC = NULL;
  74. HBITMAP hMemoryBitmap = NULL;
  75. HBITMAP hOldBitmap = NULL;
  76. HINSTANCE hInst;
  77. HDC hStatusBufferDC = NULL;
  78. HBITMAP hStatusBufferBitmap = NULL;
  79. POINT previousPoint;
  80. HDC hdc;
  81. HWND hWnd;
  82. DWORD lastStatusUpdateTime = 0;
  83. const DWORD STATUS_UPDATE_INTERVAL = 50;
  84. const int GRID_SIZE = 100;
  85. const COLORREF GRID_COLOR = RGB(255, 140, 0);
  86. HDC hGridDC = NULL;
  87. HBITMAP hGridBitmap = NULL;
  88. BOOL gridInitialized = FALSE;
  89. float gridZoomFactor = 1.0f;
  90. bool showGrid = true;
  91. bool useAlphaGrid = false;
  92. int gridOpacity = 255;
  93. COLORREF currentBrushColor = RGB(24, 123, 205);
  94. bool isInitialized = false;
  95. bool isPaintbrushSelected = true;
  96. bool isDrawing = false;
  97. bool isErasing = false;
  98. bool isClearing = false;
  99. bool isEraserMode = false;
  100. bool isEraserSelected = false;
  101. int minBrushSize = 5;
  102. int maxBrushSize = 50;
  103. int brushSize = 10;
  104. int loadProgress = 0;
  105. const wchar_t* STATE_FILE = L"canvas_state.bin";
  106.  
  107. void InitializeMemoryBitmap(HWND hwnd) {
  108.    HDC hdc = GetDC(hwnd);
  109.    if (!hdc) {
  110.        return;
  111.    }
  112.    hMemoryDC = CreateCompatibleDC(hdc);
  113.    if (!hMemoryDC) {
  114.        ReleaseDC(hwnd, hdc);
  115.        return;
  116.    }
  117.    hMemoryBitmap = CreateCompatibleBitmap(hdc, virtualWidth, virtualHeight);
  118.    if (!hMemoryBitmap) {
  119.        DeleteDC(hMemoryDC);
  120.        ReleaseDC(hwnd, hdc);
  121.        return;
  122.    }
  123.    hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hMemoryBitmap);
  124.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  125.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  126.    FillRect(hMemoryDC, &rect, whiteBrush);
  127.    DeleteObject(whiteBrush);
  128.    ReleaseDC(hwnd, hdc);
  129.    isInitialized = true;
  130.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  131.    SelectObject(hMemoryDC, guidePen);
  132.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  133.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  134.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  135.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  136.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  137.    SelectObject(hMemoryDC, edgePen);
  138.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  139.    DeleteObject(guidePen);
  140.    DeleteObject(edgePen);
  141.    RECT clientRect;
  142.    GetClientRect(hwnd, &clientRect);
  143.    scrollX = (virtualWidth - clientRect.right) / 2;
  144.    scrollY = (virtualHeight - clientRect.bottom) / 2;
  145. }
  146.  
  147. void SaveCanvasState() {
  148.    std::ofstream file(STATE_FILE, std::ios::binary | std::ios::out);
  149.    if (!file) return;
  150.  
  151.    // Save canvas settings
  152.    file.write(reinterpret_cast<const char*>(&gridZoomFactor), sizeof(float));
  153.    file.write(reinterpret_cast<const char*>(&showGrid), sizeof(bool));
  154.    file.write(reinterpret_cast<const char*>(&useAlphaGrid), sizeof(bool));
  155.    file.write(reinterpret_cast<const char*>(&gridOpacity), sizeof(int));
  156.    file.write(reinterpret_cast<const char*>(&currentBrushColor), sizeof(COLORREF));
  157.    file.write(reinterpret_cast<const char*>(&brushSize), sizeof(int));  // Save current brush size
  158.  
  159.    // Save strokes
  160.    size_t strokeCount = strokeHistory.size();
  161.    file.write(reinterpret_cast<const char*>(&strokeCount), sizeof(size_t));
  162.  
  163.    for (const auto& stroke : strokeHistory) {
  164.        size_t pointCount = stroke.points.size();
  165.        file.write(reinterpret_cast<const char*>(&pointCount), sizeof(size_t));
  166.        file.write(reinterpret_cast<const char*>(stroke.points.data()),
  167.            pointCount * sizeof(DrawPoint));
  168.        file.write(reinterpret_cast<const char*>(&stroke.color), sizeof(COLORREF));
  169.        file.write(reinterpret_cast<const char*>(&stroke.brushSize), sizeof(int));
  170.        file.write(reinterpret_cast<const char*>(&stroke.isEraser), sizeof(bool));
  171.    }
  172.    file.close();
  173. }
  174.  
  175. void LoadCanvasStateAsync(HWND hwnd) {
  176.    std::thread([hwnd]() {
  177.        std::ifstream file(STATE_FILE, std::ios::binary | std::ios::in);
  178.        if (!file) {
  179.            isLoading = false;
  180.            return;
  181.        }
  182.  
  183.        try {
  184.            // Load canvas settings
  185.            file.read(reinterpret_cast<char*>(&gridZoomFactor), sizeof(float));
  186.            file.read(reinterpret_cast<char*>(&showGrid), sizeof(bool));
  187.            file.read(reinterpret_cast<char*>(&useAlphaGrid), sizeof(bool));
  188.            file.read(reinterpret_cast<char*>(&gridOpacity), sizeof(int));
  189.            file.read(reinterpret_cast<char*>(&currentBrushColor), sizeof(COLORREF));
  190.            file.read(reinterpret_cast<char*>(&brushSize), sizeof(int));  // Load brush size
  191.  
  192.            size_t strokeCount;
  193.            file.read(reinterpret_cast<char*>(&strokeCount), sizeof(size_t));
  194.            std::vector<SerializedStroke> loadedStrokes;
  195.  
  196.            for (size_t i = 0; i < strokeCount && file.good(); i++) {
  197.                SerializedStroke stroke;
  198.                size_t pointCount;
  199.                file.read(reinterpret_cast<char*>(&pointCount), sizeof(size_t));
  200.  
  201.                if (pointCount > 0 && pointCount < 1000000) {
  202.                    stroke.points.resize(pointCount);
  203.                    file.read(reinterpret_cast<char*>(stroke.points.data()),
  204.                        pointCount * sizeof(DrawPoint));
  205.                    file.read(reinterpret_cast<char*>(&stroke.color), sizeof(COLORREF));
  206.                    file.read(reinterpret_cast<char*>(&stroke.brushSize), sizeof(int));
  207.                    file.read(reinterpret_cast<char*>(&stroke.isEraser), sizeof(bool));
  208.                    loadedStrokes.push_back(stroke);
  209.                }
  210.  
  211.                loadProgress = (int)((i + 1) * 100 / strokeCount);
  212.                PostMessage(hwnd, WM_USER + 1, 0, 0);
  213.            }
  214.  
  215.            {
  216.                std::lock_guard<std::mutex> lock(strokeMutex);
  217.                strokeHistory = std::move(loadedStrokes);
  218.            }
  219.  
  220.            // Redraw after loading all strokes
  221.            RedrawAllStrokes();
  222.        }
  223.        catch (...) {
  224.            isLoading = false;
  225.            return;
  226.        }
  227.  
  228.        file.close();
  229.        isLoading = false;
  230.        InvalidateRect(hwnd, NULL, TRUE);
  231.        }).detach();
  232. }
  233.  
  234. void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize) {
  235.    if (points.size() < 2) return;
  236.  
  237.    COLORREF color = isEraser ? RGB(255, 255, 255) : strokeColor;
  238.    HBRUSH brush = CreateSolidBrush(color);
  239.    HPEN pen = CreatePen(PS_SOLID, 1, color);
  240.    HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
  241.    HPEN oldPen = (HPEN)SelectObject(hdc, pen);
  242.  
  243.    int currentBrushSize = strokeSize;  // Use passed brush size
  244.  
  245.    Ellipse(hdc,
  246.        points[0].x - currentBrushSize,
  247.        points[0].y - currentBrushSize,
  248.        points[0].x + currentBrushSize,
  249.        points[0].y + currentBrushSize);
  250.  
  251.    for (size_t i = 1; i < points.size(); ++i) {
  252.        const DrawPoint& prev = points[i - 1];
  253.        const DrawPoint& curr = points[i];
  254.        double dx = curr.x - prev.x;
  255.        double dy = curr.y - prev.y;
  256.        double distance = sqrt(dx * dx + dy * dy);
  257.  
  258.        if (distance > 0) {
  259.            DWORD timeDiff = curr.timestamp - prev.timestamp;
  260.            double speed = distance / (timeDiff ? timeDiff : 1);
  261.            int steps = (int)(distance / (currentBrushSize * 0.3));
  262.            steps = min(max(steps, 1), (int)(distance / 2));
  263.  
  264.            if (speed > MAX_SPEED) {
  265.                steps = max(steps, (int)(distance / currentBrushSize));
  266.            }
  267.  
  268.            for (int step = 0; step <= steps; ++step) {
  269.                double t = step / (double)steps;
  270.                t = t * t * (3 - 2 * t);
  271.                int x = (int)(prev.x + dx * t);
  272.                int y = (int)(prev.y + dy * t);
  273.                int dynamicBrushSize = currentBrushSize;
  274.  
  275.                if (speed > MAX_SPEED * 0.5) {
  276.                    double speedFactor = min(speed / MAX_SPEED, 1.0);
  277.                    dynamicBrushSize = (int)(currentBrushSize * (1.0 - speedFactor * 0.3));
  278.                }
  279.  
  280.                Ellipse(hdc,
  281.                    x - dynamicBrushSize,
  282.                    y - dynamicBrushSize,
  283.                    x + dynamicBrushSize,
  284.                    y + dynamicBrushSize);
  285.            }
  286.        }
  287.    }
  288.  
  289.    SelectObject(hdc, oldBrush);
  290.    SelectObject(hdc, oldPen);
  291.    DeleteObject(brush);
  292.    DeleteObject(pen);
  293. }
  294.  
  295. void DrawBrush(HDC hdc, int x, int y, bool isEraser, bool interpolate) {
  296.    if (x < 0 || x > virtualWidth || y < 0 || y > virtualHeight) {
  297.        return;
  298.    }
  299.    COLORREF color = isEraser ? RGB(255, 255, 255) : currentBrushColor;
  300.    HBRUSH brush = CreateSolidBrush(color);
  301.    HPEN pen = CreatePen(PS_SOLID, 1, color);
  302.    HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
  303.    HPEN oldPen = (HPEN)SelectObject(hdc, pen);
  304.    Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
  305.    SelectObject(hdc, oldBrush);
  306.    SelectObject(hdc, oldPen);
  307.    DeleteObject(brush);
  308.    DeleteObject(pen);
  309. }
  310.  
  311. void StartStroke(int x, int y) {
  312.    strokeBuffer.clear();
  313.    strokeBuffer.push_back(DrawPoint(x, y));
  314. }
  315.  
  316. void AddToStroke(int x, int y) {
  317.    if (strokeBuffer.empty()) {
  318.        StartStroke(x, y);
  319.        return;
  320.    }
  321.    const DrawPoint& lastPt = strokeBuffer.back();
  322.    double dx = x - lastPt.x;
  323.    double dy = y - lastPt.y;
  324.    double distance = sqrt(dx * dx + dy * dy);
  325.    if (distance >= MIN_DISTANCE) {
  326.        strokeBuffer.push_back(DrawPoint(x, y));
  327.        while (strokeBuffer.size() > STROKE_BUFFER_SIZE) {
  328.            strokeBuffer.erase(strokeBuffer.begin());
  329.        }
  330.    }
  331. }
  332.  
  333. void EndStroke() {
  334.    strokeBuffer.clear();
  335. }
  336.  
  337. void ShowColorPicker(HWND hwnd) {
  338.    CHOOSECOLOR cc = { sizeof(CHOOSECOLOR) };
  339.    static COLORREF customColors[16] = { 0 };
  340.    cc.hwndOwner = hwnd;
  341.    cc.rgbResult = currentBrushColor;
  342.    cc.lpCustColors = customColors;
  343.    cc.Flags = CC_FULLOPEN | CC_RGBINIT;
  344.    if (ChooseColor(&cc)) {
  345.        currentBrushColor = cc.rgbResult;
  346.        UpdateStatus(hwnd);
  347.    }
  348. }
  349.  
  350. void Erase(HDC hdc, int x, int y) {
  351.    HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
  352.    SelectObject(hdc, GetStockObject(NULL_PEN));
  353.    SelectObject(hdc, hBrush);
  354.    Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
  355.    DeleteObject(hBrush);
  356. }
  357.  
  358. void ClearCanvas(HWND hwnd) {
  359.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  360.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  361.    FillRect(hMemoryDC, &rect, whiteBrush);
  362.    DeleteObject(whiteBrush);
  363.    InvalidateRect(hwnd, NULL, FALSE);
  364. }
  365.  
  366. void ClearDrawing(HWND hwnd) {
  367.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  368.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  369.    FillRect(hMemoryDC, &rect, whiteBrush);
  370.    DeleteObject(whiteBrush);
  371.    RECT clientRect;
  372.    GetClientRect(hwnd, &clientRect);
  373.    scrollX = (virtualWidth - clientRect.right) / 2;
  374.    scrollY = (virtualHeight - clientRect.bottom) / 2;
  375.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  376.    SelectObject(hMemoryDC, guidePen);
  377.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  378.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  379.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  380.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  381.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  382.    SelectObject(hMemoryDC, edgePen);
  383.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  384.    DeleteObject(guidePen);
  385.    DeleteObject(edgePen);
  386.    InvalidateRect(hwnd, NULL, FALSE);
  387. }
  388.  
  389. void InitializeGridCache(HDC hdc) {
  390.    if (hGridDC) {
  391.        DeleteDC(hGridDC);
  392.        DeleteObject(hGridBitmap);
  393.    }
  394.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  395.    hGridDC = CreateCompatibleDC(hdc);
  396.    hGridBitmap = CreateCompatibleBitmap(hdc, scaledGridSize, scaledGridSize);
  397.    HBITMAP oldBitmap = (HBITMAP)SelectObject(hGridDC, hGridBitmap);
  398.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  399.    RECT rect = { 0, 0, scaledGridSize, scaledGridSize };
  400.    FillRect(hGridDC, &rect, whiteBrush);
  401.    DeleteObject(whiteBrush);
  402.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  403.    HPEN oldPen = (HPEN)SelectObject(hGridDC, gridPen);
  404.    MoveToEx(hGridDC, scaledGridSize - 1, 0, NULL);
  405.    LineTo(hGridDC, scaledGridSize - 1, scaledGridSize);
  406.    MoveToEx(hGridDC, 0, scaledGridSize - 1, NULL);
  407.    LineTo(hGridDC, scaledGridSize, scaledGridSize - 1);
  408.    SelectObject(hGridDC, oldPen);
  409.    DeleteObject(gridPen);
  410.    gridInitialized = TRUE;
  411. }
  412.  
  413. void DrawGrid(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
  414.    SetBkMode(hdc, TRANSPARENT);
  415.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  416.    HPEN oldPen = (HPEN)SelectObject(hdc, gridPen);
  417.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  418.    int offsetX = -(scrollX % scaledGridSize);
  419.    int offsetY = -(scrollY % scaledGridSize);
  420.    for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
  421.        MoveToEx(hdc, x, 0, NULL);
  422.        LineTo(hdc, x, clientRect.bottom);
  423.    }
  424.    for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
  425.        MoveToEx(hdc, 0, y, NULL);
  426.        LineTo(hdc, clientRect.right, y);
  427.    }
  428.    SelectObject(hdc, oldPen);
  429.    DeleteObject(gridPen);
  430. }
  431.  
  432. void DrawGridWithAlpha(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
  433.    HDC tempDC = CreateCompatibleDC(hdc);
  434.    HBITMAP tempBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
  435.    HBITMAP oldTempBitmap = (HBITMAP)SelectObject(tempDC, tempBitmap);
  436.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  437.    FillRect(tempDC, &clientRect, whiteBrush);
  438.    DeleteObject(whiteBrush);
  439.    SetBkMode(tempDC, TRANSPARENT);
  440.    HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
  441.    HPEN oldPen = (HPEN)SelectObject(tempDC, gridPen);
  442.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  443.    int offsetX = -(scrollX % scaledGridSize);
  444.    int offsetY = -(scrollY % scaledGridSize);
  445.    for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
  446.        MoveToEx(tempDC, x, 0, NULL);
  447.        LineTo(tempDC, x, clientRect.bottom);
  448.    }
  449.    for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
  450.        MoveToEx(tempDC, 0, y, NULL);
  451.        LineTo(tempDC, clientRect.right, y);
  452.    }
  453.    SelectObject(tempDC, oldPen);
  454.    DeleteObject(gridPen);
  455.    BLENDFUNCTION bf;
  456.    bf.BlendOp = AC_SRC_OVER;
  457.    bf.BlendFlags = 0;
  458.    bf.SourceConstantAlpha = gridOpacity;
  459.    bf.AlphaFormat = 0;
  460.    AlphaBlend(hdc, 0, 0, clientRect.right, clientRect.bottom,
  461.        tempDC, 0, 0, clientRect.right, clientRect.bottom,
  462.        bf);
  463.    SelectObject(tempDC, oldTempBitmap);
  464.    DeleteObject(tempBitmap);
  465.    DeleteDC(tempDC);
  466. }
  467.  
  468. void InitializeStatusBuffer(HWND hStatus) {
  469.    if (hStatusBufferDC) {
  470.        DeleteDC(hStatusBufferDC);
  471.        DeleteObject(hStatusBufferBitmap);
  472.    }
  473.    HDC hdc = GetDC(hStatus);
  474.    RECT rect;
  475.    GetClientRect(hStatus, &rect);
  476.    hStatusBufferDC = CreateCompatibleDC(hdc);
  477.    hStatusBufferBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
  478.    SelectObject(hStatusBufferDC, hStatusBufferBitmap);
  479.    ReleaseDC(hStatus, hdc);
  480. }
  481.  
  482. void UpdateStatus(HWND hwnd) {
  483.    DWORD currentTime = GetTickCount();
  484.    if (currentTime - lastStatusUpdateTime < STATUS_UPDATE_INTERVAL) {
  485.        return;
  486.    }
  487.    lastStatusUpdateTime = currentTime;
  488.    HWND hStatus = GetDlgItem(hwnd, 0);
  489.    if (!hStatus) return;
  490.    if (!hStatusBufferDC) {
  491.        InitializeStatusBuffer(hStatus);
  492.    }
  493.    RECT statusRect;
  494.    GetClientRect(hStatus, &statusRect);
  495.    SendMessage(hStatus, WM_ERASEBKGND, (WPARAM)hStatusBufferDC, 0);
  496.    wchar_t status[512];
  497.    BYTE r = GetRValue(currentBrushColor);
  498.    BYTE g = GetGValue(currentBrushColor);
  499.    BYTE b = GetBValue(currentBrushColor);
  500.    int centerX = scrollX + (virtualWidth / 2);
  501.    int centerY = scrollY + (virtualHeight / 2);
  502.    swprintf_s(status,
  503.        L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s%s | Zoom: %.1fx | Opacity: %d%% | Canvas Pos: (%d,%d)",
  504.        isEraserMode ? L"Eraser" : L"Draw",
  505.        brushSize,
  506.        r, g, b,
  507.        showGrid ? L"On" : L"Off",
  508.        useAlphaGrid ? L"(Alpha)" : L"",
  509.        gridZoomFactor,
  510.        (gridOpacity * 100) / 255,
  511.        centerX, centerY
  512.    );
  513.    HDC hdc = GetDC(hStatus);
  514.    SetBkMode(hStatusBufferDC, TRANSPARENT);
  515.    SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)status);
  516.    BitBlt(hdc, 0, 0, statusRect.right, statusRect.bottom,
  517.        hStatusBufferDC, 0, 0, SRCCOPY);
  518.    ReleaseDC(hStatus, hdc);
  519. }
  520.  
  521. void RedrawAllStrokes() {
  522.    if (!hMemoryDC) return;
  523.  
  524.    // Clear the memory DC first
  525.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  526.    RECT rect = { 0, 0, virtualWidth, virtualHeight };
  527.    FillRect(hMemoryDC, &rect, whiteBrush);
  528.    DeleteObject(whiteBrush);
  529.  
  530.    // Redraw guide lines
  531.    HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
  532.    SelectObject(hMemoryDC, guidePen);
  533.    MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
  534.    LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
  535.    MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
  536.    LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
  537.    DeleteObject(guidePen);
  538.  
  539.    // Redraw border
  540.    HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
  541.    SelectObject(hMemoryDC, edgePen);
  542.    Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
  543.    DeleteObject(edgePen);
  544.  
  545.    // Redraw all strokes
  546.    std::lock_guard<std::mutex> lock(strokeMutex);
  547.    for (const auto& stroke : strokeHistory) {
  548.        DrawSmoothStroke(hMemoryDC, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize);
  549.    }
  550. }
  551.  
  552. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  553.    switch (uMsg) {
  554.    case WM_CREATE:
  555.    {
  556.        InitializeMemoryBitmap(hwnd);
  557.        if (hMemoryDC) {
  558.            HBRUSH redBrush = CreateSolidBrush(RGB(255, 0, 0));
  559.            RECT testRect = { 100, 100, 200, 200 };
  560.            FillRect(hMemoryDC, &testRect, redBrush);
  561.            DeleteObject(redBrush);
  562.            InvalidateRect(hwnd, NULL, FALSE);
  563.        }
  564.        else {
  565.            MessageBox(hwnd, L"Memory DC failed to initialize", L"Error", MB_OK);
  566.        }
  567.        UpdateStatus(hwnd);
  568.        isLoading = true;
  569.        LoadCanvasStateAsync(hwnd);
  570.        return 0;
  571.    }
  572.    case WM_SYSCOMMAND:
  573.    {
  574.        if (wParam == SC_CLOSE) {
  575.            DestroyWindow(hwnd);
  576.            return 0;
  577.        }
  578.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  579.    }
  580.    case WM_KEYDOWN:
  581.    {
  582.        if (GetKeyState(VK_MENU) & 0x8000) {
  583.            return DefWindowProc(hwnd, uMsg, wParam, lParam);
  584.        }
  585.        if (wParam == VK_SPACE && !isSpacePressed) {
  586.            isSpacePressed = true;
  587.            GetCursorPos(&lastMousePos);
  588.            ScreenToClient(hwnd, &lastMousePos);
  589.            SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  590.            SetCapture(hwnd);
  591.            return 0;
  592.        }
  593.        else if (wParam == 0x50) {
  594.            isPaintbrushSelected = true;
  595.            isEraserMode = false;
  596.            UpdateStatus(hwnd);
  597.        }
  598.        else if (wParam == 0x45) {
  599.            isPaintbrushSelected = false;
  600.            isEraserMode = true;
  601.            UpdateStatus(hwnd);
  602.        }
  603.        else if (wParam == 'Q') {
  604.            ShowColorPicker(hwnd);
  605.        }
  606.        else if (wParam == VK_ADD || wParam == VK_OEM_PLUS) {
  607.            brushSize = std::min(50, brushSize + 5);
  608.            UpdateStatus(hwnd);
  609.        }
  610.        else if (wParam == VK_SUBTRACT || wParam == VK_OEM_MINUS) {
  611.            brushSize = std::max(5, brushSize - 5);
  612.            UpdateStatus(hwnd);
  613.        }
  614.        else if (wParam == 0x43) {
  615.            if (!(GetKeyState(VK_CONTROL) & 0x8000)) {
  616.                ClearDrawing(hwnd);
  617.            }
  618.        }
  619.        else if (wParam == VK_HOME) {
  620.            RECT clientRect;
  621.            GetClientRect(hwnd, &clientRect);
  622.            scrollX = (virtualWidth - clientRect.right) / 2;
  623.            scrollY = (virtualHeight - clientRect.bottom) / 2;
  624.            InvalidateRect(hwnd, NULL, FALSE);
  625.        }
  626.        else if (wParam == 'G') {
  627.            showGrid = !showGrid;
  628.            InvalidateRect(hwnd, NULL, FALSE);
  629.            UpdateStatus(hwnd);
  630.        }
  631.        else if (wParam == 'A') {
  632.            useAlphaGrid = !useAlphaGrid;
  633.            InvalidateRect(hwnd, NULL, FALSE);
  634.            UpdateStatus(hwnd);
  635.        }
  636.        if (wParam == VK_PRIOR) {
  637.            gridZoomFactor *= 1.1f;
  638.            gridInitialized = FALSE;
  639.            InvalidateRect(hwnd, NULL, FALSE);
  640.            UpdateStatus(hwnd);
  641.        }
  642.        else if (wParam == VK_NEXT) {
  643.            gridZoomFactor *= 0.9f;
  644.            if (gridZoomFactor < 0.1f) gridZoomFactor = 0.1f;
  645.            gridInitialized = FALSE;
  646.            InvalidateRect(hwnd, NULL, FALSE);
  647.            UpdateStatus(hwnd);
  648.        }
  649.        else if (wParam == VK_OEM_6 && useAlphaGrid) {
  650.            gridOpacity = min(255, gridOpacity + 15);
  651.            InvalidateRect(hwnd, NULL, FALSE);
  652.            UpdateStatus(hwnd);
  653.        }
  654.        else if (wParam == VK_OEM_4 && useAlphaGrid) {
  655.            gridOpacity = max(0, gridOpacity - 15);
  656.            InvalidateRect(hwnd, NULL, FALSE);
  657.            UpdateStatus(hwnd);
  658.        }
  659.        else if (wParam == VK_ESCAPE) {
  660.            if (!strokeBuffer.empty()) {  // Only save if there are points
  661.                SaveCanvasState();  // Save canvas state before exiting
  662.            }
  663.            PostQuitMessage(0);
  664.            return 0;
  665.        }
  666.        else if (wParam == VK_F1) {
  667.            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);
  668.        }
  669.        return 0;
  670.    }
  671.    case WM_KEYUP:
  672.    {
  673.        if (wParam == VK_SPACE) {
  674.            isSpacePressed = false;
  675.            SetCursor(LoadCursor(NULL, IDC_ARROW));
  676.            ReleaseCapture();
  677.            return 0;
  678.        }
  679.    }
  680.    case WM_LBUTTONDOWN:
  681.    {
  682.        if (!isSpacePressed) {
  683.            isDrawing = true;
  684.            SetCapture(hwnd);
  685.            int x = LOWORD(lParam);
  686.            int y = HIWORD(lParam);
  687.            int canvasX = x + scrollX;
  688.            int canvasY = y + scrollY;
  689.            if (canvasX >= 0 && canvasX <= virtualWidth &&
  690.                canvasY >= 0 && canvasY <= virtualHeight) {
  691.                StartStroke(canvasX, canvasY);
  692.                DrawBrush(hMemoryDC, canvasX, canvasY, isEraserMode, false);
  693.                HDC screenDC = GetDC(hwnd);
  694.                DrawBrush(screenDC, x, y, isEraserMode, false);
  695.                ReleaseDC(hwnd, screenDC);
  696.            }
  697.        }
  698.        return 0;
  699.    }
  700.    // In WM_LBUTTONUP case:
  701.    case WM_LBUTTONUP:
  702.    {
  703.        if (isPanning) {
  704.            isPanning = false;
  705.            ReleaseCapture();
  706.        }
  707.        if (isDrawing) {
  708.            isDrawing = false;
  709.            if (!strokeBuffer.empty()) {  // Only save if there are points
  710.                SerializedStroke stroke;
  711.                stroke.points = strokeBuffer;  // Save all accumulated points
  712.                stroke.color = currentBrushColor;
  713.                stroke.brushSize = brushSize;
  714.                stroke.isEraser = isEraserMode;
  715.                {
  716.                    std::lock_guard<std::mutex> lock(strokeMutex);
  717.                    strokeHistory.push_back(stroke);
  718.                }
  719.                SaveCanvasState();  // Save after each stroke
  720.            }
  721.            EndStroke();
  722.            ReleaseCapture();
  723.        }
  724.        return 0;
  725.    }
  726.    case WM_MOUSEMOVE:
  727.    {
  728.        int x = LOWORD(lParam);
  729.        int y = HIWORD(lParam);
  730.        if (isSpacePressed) {
  731.            RECT clientRect;
  732.            GetClientRect(hwnd, &clientRect);
  733.            int deltaX = x - lastMousePos.x;
  734.            int deltaY = y - lastMousePos.y;
  735.            int newScrollX = std::max(0, std::min<int>(virtualWidth - clientRect.right, scrollX - deltaX));
  736.            int newScrollY = std::max(0, std::min<int>(virtualHeight - clientRect.bottom, scrollY - deltaY));
  737.            if (newScrollX != scrollX || newScrollY != scrollY) {
  738.                scrollX = newScrollX;
  739.                scrollY = newScrollY;
  740.                ScrollWindowEx(hwnd, deltaX, deltaY,
  741.                    NULL, NULL, NULL, NULL,
  742.                    SW_INVALIDATE | SW_ERASE);
  743.                if (abs(deltaX) > 5 || abs(deltaY) > 5) {
  744.                    UpdateStatus(hwnd);
  745.                }
  746.            }
  747.            lastMousePos.x = x;
  748.            lastMousePos.y = y;
  749.        }
  750.        else if (isDrawing && (wParam & MK_LBUTTON)) {
  751.            int canvasX = x + scrollX;
  752.            int canvasY = y + scrollY;
  753.            if (canvasX >= 0 && canvasX <= virtualWidth &&
  754.                canvasY >= 0 && canvasY <= virtualHeight) {
  755.  
  756.                DrawPoint newPoint(canvasX, canvasY);
  757.                strokeBuffer.push_back(newPoint);  // Add point to buffer
  758.  
  759.                // Draw to memory DC
  760.                DrawSmoothStroke(hMemoryDC, strokeBuffer, isEraserMode, currentBrushColor, brushSize);
  761.  
  762.                // Draw to screen
  763.                HDC screenDC = GetDC(hwnd);
  764.                std::vector<DrawPoint> screenPoints;
  765.                for (const auto& pt : strokeBuffer) {
  766.                    screenPoints.push_back(DrawPoint(pt.x - scrollX, pt.y - scrollY));
  767.                }
  768.                DrawSmoothStroke(screenDC, screenPoints, isEraserMode, currentBrushColor, brushSize);
  769.                ReleaseDC(hwnd, screenDC);
  770.  
  771.                if (GetTickCount() - lastStatusUpdateTime > STATUS_UPDATE_INTERVAL * 2) {
  772.                    UpdateStatus(hwnd);
  773.                }
  774.            }
  775.        }
  776.        return 0;
  777.    }
  778.    case WM_SIZE:
  779.    {
  780.        HWND hStatus = GetDlgItem(hwnd, 0);
  781.        if (hStatus) {
  782.            SendMessage(hStatus, WM_SIZE, 0, 0);
  783.            InitializeStatusBuffer(hStatus);
  784.            UpdateStatus(hwnd);
  785.        }
  786.        if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {
  787.            InvalidateRect(hwnd, NULL, TRUE);
  788.        }
  789.        return 0;
  790.    }
  791.    case WM_PAINT:
  792.    {
  793.        PAINTSTRUCT ps;
  794.        HDC hdc = BeginPaint(hwnd, &ps);
  795.        RECT clientRect;
  796.        GetClientRect(hwnd, &clientRect);
  797.        int windowWidth = clientRect.right - clientRect.left;
  798.        int windowHeight = clientRect.bottom - clientRect.top;
  799.  
  800.        HDC memDC = CreateCompatibleDC(hdc);
  801.        HBITMAP memBitmap = CreateCompatibleBitmap(hdc, windowWidth, windowHeight);
  802.        HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
  803.  
  804.        HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  805.        FillRect(memDC, &clientRect, whiteBrush);
  806.        DeleteObject(whiteBrush);
  807.  
  808.        BitBlt(memDC, 0, 0, windowWidth, windowHeight, hMemoryDC, scrollX, scrollY, SRCCOPY);
  809.  
  810.        if (showGrid) {
  811.            if (useAlphaGrid) {
  812.                DrawGridWithAlpha(memDC, clientRect, scrollX, scrollY);
  813.            }
  814.            else {
  815.                DrawGrid(memDC, clientRect, scrollX, scrollY);
  816.            }
  817.        }
  818.  
  819.        BitBlt(hdc, 0, 0, windowWidth, windowHeight, memDC, 0, 0, SRCCOPY);
  820.  
  821.        SelectObject(memDC, oldBitmap);
  822.        DeleteObject(memBitmap);
  823.        DeleteDC(memDC);
  824.  
  825.        EndPaint(hwnd, &ps);
  826.        return 0;
  827.    }
  828.    case WM_SETCURSOR:
  829.    {
  830.        if (LOWORD(lParam) == HTCLIENT) {
  831.            if (isSpacePressed) {
  832.                SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  833.                return TRUE;
  834.            }
  835.            else if (isPaintbrushSelected || isEraserMode) {
  836.                SetCursor(LoadCursor(NULL, IDC_CROSS));
  837.                return TRUE;
  838.            }
  839.        }
  840.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  841.    }
  842.    case WM_MOUSEWHEEL:
  843.    {
  844.        int delta = GET_WHEEL_DELTA_WPARAM(wParam);
  845.        if (GetKeyState(VK_CONTROL) & 0x8000) {
  846.            if (delta > 0)
  847.                gridZoomFactor *= 1.1f;
  848.            else
  849.                gridZoomFactor *= 0.9f;
  850.            InvalidateRect(hwnd, NULL, FALSE);
  851.        }
  852.        return 0;
  853.    }
  854.    case WM_DESTROY:
  855.    {
  856.        // Save the canvas state before exiting
  857.        if (!strokeHistory.empty()) {
  858.            SaveCanvasState();
  859.        }
  860.  
  861.        // Clean up resources
  862.        if (hGridDC) {
  863.            DeleteDC(hGridDC);
  864.            hGridDC = NULL;
  865.        }
  866.        if (hGridBitmap) {
  867.            DeleteObject(hGridBitmap);
  868.            hGridBitmap = NULL;
  869.        }
  870.        if (hMemoryDC) {
  871.            if (hOldBitmap) {
  872.                SelectObject(hMemoryDC, hOldBitmap);
  873.                hOldBitmap = NULL;
  874.            }
  875.            DeleteDC(hMemoryDC);
  876.            hMemoryDC = NULL;
  877.        }
  878.        if (hMemoryBitmap) {
  879.            DeleteObject(hMemoryBitmap);
  880.            hMemoryBitmap = NULL;
  881.        }
  882.        if (hStatusBufferDC) {
  883.            DeleteDC(hStatusBufferDC);
  884.            hStatusBufferDC = NULL;
  885.        }
  886.        if (hStatusBufferBitmap) {
  887.            DeleteObject(hStatusBufferBitmap);
  888.            hStatusBufferBitmap = NULL;
  889.        }
  890.  
  891.        PostQuitMessage(0);
  892.        return 0;
  893.    }
  894.    default:
  895.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  896.    }
  897.    return 0;
  898. }
  899.  
  900. INITCOMMONCONTROLSEX icex;
  901.  
  902. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
  903.    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
  904.    icex.dwICC = ICC_BAR_CLASSES;
  905.    InitCommonControlsEx(&icex);
  906.  
  907.    const wchar_t CLASS_NAME[] = L"DoodleAppClass";
  908.  
  909.    WNDCLASS wc = { };
  910.    wc.lpfnWndProc = WindowProc;
  911.    wc.hInstance = hInstance;
  912.    wc.lpszClassName = CLASS_NAME;
  913.    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
  914.  
  915.    RegisterClass(&wc);
  916.  
  917.    hInst = hInstance;
  918.  
  919.    HWND hwnd = CreateWindowEx(
  920.        0,
  921.        CLASS_NAME,
  922.        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)",
  923.        WS_OVERLAPPEDWINDOW | WS_MAXIMIZE,
  924.        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  925.        NULL,
  926.        NULL,
  927.        hInstance,
  928.        NULL
  929.    );
  930.  
  931.    SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_COMPOSITED);
  932.  
  933.    if (hwnd == NULL) {
  934.        return 0;
  935.    }
  936.  
  937.    HWND hStatus = CreateWindowEx(
  938.        0,
  939.        STATUSCLASSNAME,
  940.        NULL,
  941.        WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
  942.        0, 0, 0, 0,
  943.        hwnd,
  944.        (HMENU)0,
  945.        hInstance,
  946.        NULL
  947.    );
  948.  
  949.    if (hStatus) {
  950.        int statwidths[] = { -1 };
  951.        SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)statwidths);
  952.        UpdateStatus(hwnd);
  953.    }
  954.  
  955.    ShowWindow(hwnd, SW_SHOWMAXIMIZED);
  956.  
  957.    MSG msg = {};
  958.    while (GetMessage(&msg, NULL, 0, 0)) {
  959.        TranslateMessage(&msg);
  960.        DispatchMessage(&msg);
  961.    }
  962.  
  963.    return 0;
  964. }
Add Comment
Please, Sign In to add comment