Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ==++ Here's the source code for (file 1/1) "main.cpp":: ++==
- #define NOMINMAX
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <windef.h>
- #include <wingdi.h>
- #include <algorithm>
- #include <iostream>
- #include <vector>
- #include <windowsx.h> // For GET_X_LPARAM and GET_Y_LPARAM
- #include <cmath>
- #include <commctrl.h>
- #include <commdlg.h>
- #include <stdio.h>
- #include <cmath>
- #include <fstream>
- #include <thread>
- #include <mutex>
- #include "resource.h"
- #pragma comment(lib, "comctl32.lib")
- #pragma comment(lib, "comdlg32.lib")
- #pragma comment(lib, "msimg32.lib")
- using namespace std;
- struct DrawPoint {
- int x, y;
- DWORD timestamp;
- DrawPoint() : x(0), y(0), timestamp(0) {}
- DrawPoint(int px, int py) : x(px), y(py), timestamp(GetTickCount()) {}
- };
- struct SerializedStroke {
- std::vector<DrawPoint> points;
- COLORREF color;
- int brushSize;
- bool isEraser;
- };
- struct CanvasState {
- std::vector<SerializedStroke> strokes;
- float gridZoomFactor;
- bool showGrid;
- bool useAlphaGrid;
- int gridOpacity;
- };
- // Add after your existing structs
- struct CompressedStroke {
- std::vector<DrawPoint> points;
- COLORREF color;
- int brushSize;
- bool isEraser;
- bool isCompressed;
- };
- struct BatchRenderItem {
- std::vector<DrawPoint> points;
- COLORREF color;
- int brushSize;
- bool isEraser;
- };
- std::mutex strokeMutex;
- std::vector<SerializedStroke> strokeHistory;
- bool isLoading = false;
- const double MAX_SPEED = 1000.0;
- std::vector<DrawPoint> strokeBuffer;
- const int STROKE_BUFFER_SIZE = 3;
- const double MIN_DISTANCE = 2.0;
- void CompressStroke(CompressedStroke& stroke, double tolerance = 2.0);
- void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize);
- void DrawBrush(HDC hdc, int x, int y, bool isEraser = false, bool interpolate = true);
- void Erase(HDC hdc, int x, int y);
- void ClearDrawing(HWND hwnd);
- void UpdateStatus(HWND hwnd);
- void RedrawAllStrokes();
- void ProcessBatch(HDC hdc);
- LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
- int virtualWidth = 8192;
- int virtualHeight = 8192;
- int scrollX = 0;
- int scrollY = 0;
- bool isSpacePressed = false;
- bool isPanning = false;
- POINT lastDrawPoint = { -1, -1 };
- POINT lastMousePos = { 0, 0 };
- POINT dragStart = { 0, 0 };
- HDC hMemoryDC = NULL;
- HBITMAP hMemoryBitmap = NULL;
- HBITMAP hOldBitmap = NULL;
- HINSTANCE hInst;
- HDC hStatusBufferDC = NULL;
- HBITMAP hStatusBufferBitmap = NULL;
- POINT previousPoint;
- HDC hdc;
- HWND hWnd;
- DWORD lastStatusUpdateTime = 0;
- const DWORD STATUS_UPDATE_INTERVAL = 50;
- const int GRID_SIZE = 100;
- const COLORREF GRID_COLOR = RGB(255, 140, 0);
- HDC hGridDC = NULL;
- HBITMAP hGridBitmap = NULL;
- BOOL gridInitialized = FALSE;
- float gridZoomFactor = 1.0f;
- bool showGrid = true;
- bool useAlphaGrid = false;
- int gridOpacity = 255;
- COLORREF currentBrushColor = RGB(24, 123, 205);
- bool isInitialized = false;
- bool isPaintbrushSelected = true;
- bool isDrawing = false;
- bool isErasing = false;
- bool isClearing = false;
- bool isEraserMode = false;
- bool isEraserSelected = false;
- int minBrushSize = 5;
- int maxBrushSize = 50;
- int brushSize = 10;
- int loadProgress = 0;
- const wchar_t* STATE_FILE = L"canvas_state.bin";
- // Add these globals after your existing globals
- std::vector<BatchRenderItem> renderBatch;
- const size_t BATCH_SIZE = 100;
- HANDLE hMapFile = NULL;
- LPVOID pMapView = NULL;
- const size_t MAP_SIZE = 1024 * 1024 * 1024; // 1GB
- std::vector<CompressedStroke> compressedStrokes;
- size_t lastSavedStrokeIndex = 0;
- void InitializeMemoryBitmap(HWND hwnd) {
- HDC hdc = GetDC(hwnd);
- if (!hdc) {
- return;
- }
- hMemoryDC = CreateCompatibleDC(hdc);
- if (!hMemoryDC) {
- ReleaseDC(hwnd, hdc);
- return;
- }
- hMemoryBitmap = CreateCompatibleBitmap(hdc, virtualWidth, virtualHeight);
- if (!hMemoryBitmap) {
- DeleteDC(hMemoryDC);
- ReleaseDC(hwnd, hdc);
- return;
- }
- hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hMemoryBitmap);
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- RECT rect = { 0, 0, virtualWidth, virtualHeight };
- FillRect(hMemoryDC, &rect, whiteBrush);
- DeleteObject(whiteBrush);
- ReleaseDC(hwnd, hdc);
- isInitialized = true;
- HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
- SelectObject(hMemoryDC, guidePen);
- MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
- LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
- MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
- LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
- HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
- SelectObject(hMemoryDC, edgePen);
- Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
- DeleteObject(guidePen);
- DeleteObject(edgePen);
- RECT clientRect;
- GetClientRect(hwnd, &clientRect);
- scrollX = (virtualWidth - clientRect.right) / 2;
- scrollY = (virtualHeight - clientRect.bottom) / 2;
- }
- void LoadCanvasStateAsync(HWND hwnd) {
- std::thread([hwnd]() {
- std::ifstream file(STATE_FILE, std::ios::binary | std::ios::in);
- if (!file) {
- isLoading = false;
- return;
- }
- try {
- // Load canvas settings
- file.read(reinterpret_cast<char*>(&gridZoomFactor), sizeof(float));
- file.read(reinterpret_cast<char*>(&showGrid), sizeof(bool));
- file.read(reinterpret_cast<char*>(&useAlphaGrid), sizeof(bool));
- file.read(reinterpret_cast<char*>(&gridOpacity), sizeof(int));
- file.read(reinterpret_cast<char*>(¤tBrushColor), sizeof(COLORREF));
- file.read(reinterpret_cast<char*>(&brushSize), sizeof(int));
- size_t strokeCount;
- file.read(reinterpret_cast<char*>(&strokeCount), sizeof(size_t));
- std::vector<SerializedStroke> loadedStrokes;
- for (size_t i = 0; i < strokeCount && file.good(); ++i) {
- SerializedStroke stroke;
- size_t pointCount;
- file.read(reinterpret_cast<char*>(&pointCount), sizeof(size_t));
- if (pointCount > 0 && pointCount < 1000000) {
- // Read points individually
- for (size_t j = 0; j < pointCount; ++j) {
- DrawPoint point;
- file.read(reinterpret_cast<char*>(&point.x), sizeof(int));
- file.read(reinterpret_cast<char*>(&point.y), sizeof(int));
- file.read(reinterpret_cast<char*>(&point.timestamp), sizeof(DWORD));
- stroke.points.push_back(point);
- }
- file.read(reinterpret_cast<char*>(&stroke.color), sizeof(COLORREF));
- file.read(reinterpret_cast<char*>(&stroke.brushSize), sizeof(int));
- file.read(reinterpret_cast<char*>(&stroke.isEraser), sizeof(bool));
- loadedStrokes.push_back(stroke);
- }
- }
- {
- std::lock_guard<std::mutex> lock(strokeMutex);
- strokeHistory = std::move(loadedStrokes);
- }
- RedrawAllStrokes();
- }
- catch (...) {
- isLoading = false;
- return;
- }
- file.close();
- isLoading = false;
- InvalidateRect(hwnd, NULL, TRUE);
- }).detach();
- }
- void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize) {
- if (points.empty()) return;
- COLORREF color = isEraser ? RGB(255, 255, 255) : strokeColor;
- HBRUSH brush = CreateSolidBrush(color);
- HPEN pen = CreatePen(PS_SOLID, 1, color);
- HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
- HPEN oldPen = (HPEN)SelectObject(hdc, pen);
- // Handle single point
- if (points.size() == 1) {
- const DrawPoint& pt = points[0];
- Ellipse(hdc,
- pt.x - strokeSize,
- pt.y - strokeSize,
- pt.x + strokeSize,
- pt.y + strokeSize);
- }
- else {
- // Original smooth stroke drawing code for multiple points
- for (size_t i = 1; i < points.size(); ++i) {
- const DrawPoint& prev = points[i - 1];
- const DrawPoint& curr = points[i];
- double dx = curr.x - prev.x;
- double dy = curr.y - prev.y;
- double distance = sqrt(dx * dx + dy * dy);
- if (distance > 0) {
- int steps = max(1, (int)(distance / 2));
- for (int step = 0; step <= steps; ++step) {
- double t = step / (double)steps;
- int x = (int)(prev.x + dx * t);
- int y = (int)(prev.y + dy * t);
- Ellipse(hdc,
- x - strokeSize,
- y - strokeSize,
- x + strokeSize,
- y + strokeSize);
- }
- }
- }
- }
- SelectObject(hdc, oldBrush);
- SelectObject(hdc, oldPen);
- DeleteObject(brush);
- DeleteObject(pen);
- }
- void DrawBrush(HDC hdc, int x, int y, bool isEraser, bool interpolate) {
- if (x < 0 || x > virtualWidth || y < 0 || y > virtualHeight) {
- return;
- }
- COLORREF color = isEraser ? RGB(255, 255, 255) : currentBrushColor;
- HBRUSH brush = CreateSolidBrush(color);
- HPEN pen = CreatePen(PS_SOLID, 1, color);
- HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
- HPEN oldPen = (HPEN)SelectObject(hdc, pen);
- Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
- SelectObject(hdc, oldBrush);
- SelectObject(hdc, oldPen);
- DeleteObject(brush);
- DeleteObject(pen);
- }
- void StartStroke(int x, int y) {
- strokeBuffer.clear();
- strokeBuffer.push_back(DrawPoint(x, y));
- }
- // Modify the AddToStroke function to ensure proper point spacing
- void AddToStroke(int x, int y) {
- if (strokeBuffer.empty()) {
- StartStroke(x, y);
- return;
- }
- const DrawPoint& lastPt = strokeBuffer.back();
- double dx = x - lastPt.x;
- double dy = y - lastPt.y;
- double distance = sqrt(dx * dx + dy * dy);
- // Add intermediate points if the distance is too large
- if (distance >= MIN_DISTANCE) {
- int steps = (int)(distance / MIN_DISTANCE);
- for (int i = 1; i <= steps; ++i) {
- double t = i / (double)steps;
- int interpX = (int)(lastPt.x + dx * t);
- int interpY = (int)(lastPt.y + dy * t);
- strokeBuffer.push_back(DrawPoint(interpX, interpY));
- }
- }
- }
- void EndStroke() {
- strokeBuffer.clear();
- }
- void ShowColorPicker(HWND hwnd) {
- CHOOSECOLOR cc = { sizeof(CHOOSECOLOR) };
- static COLORREF customColors[16] = { 0 };
- cc.hwndOwner = hwnd;
- cc.rgbResult = currentBrushColor;
- cc.lpCustColors = customColors;
- cc.Flags = CC_FULLOPEN | CC_RGBINIT;
- if (ChooseColor(&cc)) {
- currentBrushColor = cc.rgbResult;
- UpdateStatus(hwnd);
- }
- }
- void Erase(HDC hdc, int x, int y) {
- HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
- SelectObject(hdc, GetStockObject(NULL_PEN));
- SelectObject(hdc, hBrush);
- Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
- DeleteObject(hBrush);
- }
- void ClearCanvas(HWND hwnd) {
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- RECT rect = { 0, 0, virtualWidth, virtualHeight };
- FillRect(hMemoryDC, &rect, whiteBrush);
- DeleteObject(whiteBrush);
- InvalidateRect(hwnd, NULL, FALSE);
- }
- void ClearDrawing(HWND hwnd) {
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- RECT rect = { 0, 0, virtualWidth, virtualHeight };
- FillRect(hMemoryDC, &rect, whiteBrush);
- DeleteObject(whiteBrush);
- RECT clientRect;
- GetClientRect(hwnd, &clientRect);
- scrollX = (virtualWidth - clientRect.right) / 2;
- scrollY = (virtualHeight - clientRect.bottom) / 2;
- HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
- SelectObject(hMemoryDC, guidePen);
- MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
- LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
- MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
- LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
- HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
- SelectObject(hMemoryDC, edgePen);
- Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
- DeleteObject(guidePen);
- DeleteObject(edgePen);
- InvalidateRect(hwnd, NULL, FALSE);
- }
- void InitializeGridCache(HDC hdc) {
- if (hGridDC) {
- DeleteDC(hGridDC);
- DeleteObject(hGridBitmap);
- }
- int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
- hGridDC = CreateCompatibleDC(hdc);
- hGridBitmap = CreateCompatibleBitmap(hdc, scaledGridSize, scaledGridSize);
- HBITMAP oldBitmap = (HBITMAP)SelectObject(hGridDC, hGridBitmap);
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- RECT rect = { 0, 0, scaledGridSize, scaledGridSize };
- FillRect(hGridDC, &rect, whiteBrush);
- DeleteObject(whiteBrush);
- HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
- HPEN oldPen = (HPEN)SelectObject(hGridDC, gridPen);
- MoveToEx(hGridDC, scaledGridSize - 1, 0, NULL);
- LineTo(hGridDC, scaledGridSize - 1, scaledGridSize);
- MoveToEx(hGridDC, 0, scaledGridSize - 1, NULL);
- LineTo(hGridDC, scaledGridSize, scaledGridSize - 1);
- SelectObject(hGridDC, oldPen);
- DeleteObject(gridPen);
- gridInitialized = TRUE;
- }
- void DrawGrid(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
- SetBkMode(hdc, TRANSPARENT);
- HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
- HPEN oldPen = (HPEN)SelectObject(hdc, gridPen);
- int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
- int offsetX = -(scrollX % scaledGridSize);
- int offsetY = -(scrollY % scaledGridSize);
- for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
- MoveToEx(hdc, x, 0, NULL);
- LineTo(hdc, x, clientRect.bottom);
- }
- for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
- MoveToEx(hdc, 0, y, NULL);
- LineTo(hdc, clientRect.right, y);
- }
- SelectObject(hdc, oldPen);
- DeleteObject(gridPen);
- }
- void DrawGridWithAlpha(HDC hdc, const RECT& clientRect, int scrollX, int scrollY) {
- HDC tempDC = CreateCompatibleDC(hdc);
- HBITMAP tempBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
- HBITMAP oldTempBitmap = (HBITMAP)SelectObject(tempDC, tempBitmap);
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- FillRect(tempDC, &clientRect, whiteBrush);
- DeleteObject(whiteBrush);
- SetBkMode(tempDC, TRANSPARENT);
- HPEN gridPen = CreatePen(PS_SOLID, 1, GRID_COLOR);
- HPEN oldPen = (HPEN)SelectObject(tempDC, gridPen);
- int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
- int offsetX = -(scrollX % scaledGridSize);
- int offsetY = -(scrollY % scaledGridSize);
- for (int x = offsetX; x <= clientRect.right; x += scaledGridSize) {
- MoveToEx(tempDC, x, 0, NULL);
- LineTo(tempDC, x, clientRect.bottom);
- }
- for (int y = offsetY; y <= clientRect.bottom; y += scaledGridSize) {
- MoveToEx(tempDC, 0, y, NULL);
- LineTo(tempDC, clientRect.right, y);
- }
- SelectObject(tempDC, oldPen);
- DeleteObject(gridPen);
- BLENDFUNCTION bf;
- bf.BlendOp = AC_SRC_OVER;
- bf.BlendFlags = 0;
- bf.SourceConstantAlpha = gridOpacity;
- bf.AlphaFormat = 0;
- AlphaBlend(hdc, 0, 0, clientRect.right, clientRect.bottom,
- tempDC, 0, 0, clientRect.right, clientRect.bottom,
- bf);
- SelectObject(tempDC, oldTempBitmap);
- DeleteObject(tempBitmap);
- DeleteDC(tempDC);
- }
- void InitializeStatusBuffer(HWND hStatus) {
- if (hStatusBufferDC) {
- DeleteDC(hStatusBufferDC);
- DeleteObject(hStatusBufferBitmap);
- }
- HDC hdc = GetDC(hStatus);
- RECT rect;
- GetClientRect(hStatus, &rect);
- hStatusBufferDC = CreateCompatibleDC(hdc);
- hStatusBufferBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
- SelectObject(hStatusBufferDC, hStatusBufferBitmap);
- ReleaseDC(hStatus, hdc);
- }
- void UpdateStatus(HWND hwnd) {
- DWORD currentTime = GetTickCount();
- if (currentTime - lastStatusUpdateTime < STATUS_UPDATE_INTERVAL) {
- return;
- }
- lastStatusUpdateTime = currentTime;
- HWND hStatus = GetDlgItem(hwnd, 0);
- if (!hStatus) return;
- if (!hStatusBufferDC) {
- InitializeStatusBuffer(hStatus);
- }
- RECT statusRect;
- GetClientRect(hStatus, &statusRect);
- SendMessage(hStatus, WM_ERASEBKGND, (WPARAM)hStatusBufferDC, 0);
- wchar_t status[512];
- BYTE r = GetRValue(currentBrushColor);
- BYTE g = GetGValue(currentBrushColor);
- BYTE b = GetBValue(currentBrushColor);
- int centerX = scrollX + (virtualWidth / 2);
- int centerY = scrollY + (virtualHeight / 2);
- swprintf_s(status,
- L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s%s | Zoom: %.1fx | Opacity: %d%% | Canvas Pos: (%d,%d)",
- isEraserMode ? L"Eraser" : L"Draw",
- brushSize,
- r, g, b,
- showGrid ? L"On" : L"Off",
- useAlphaGrid ? L"(Alpha)" : L"",
- gridZoomFactor,
- (gridOpacity * 100) / 255,
- centerX, centerY
- );
- HDC hdc = GetDC(hStatus);
- SetBkMode(hStatusBufferDC, TRANSPARENT);
- SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM)status);
- BitBlt(hdc, 0, 0, statusRect.right, statusRect.bottom,
- hStatusBufferDC, 0, 0, SRCCOPY);
- ReleaseDC(hStatus, hdc);
- }
- void RedrawAllStrokes() {
- if (!hMemoryDC) return;
- // Clear the canvas with white background
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- RECT rect = { 0, 0, virtualWidth, virtualHeight };
- FillRect(hMemoryDC, &rect, whiteBrush);
- DeleteObject(whiteBrush);
- // Draw guide lines
- HPEN guidePen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
- SelectObject(hMemoryDC, guidePen);
- MoveToEx(hMemoryDC, virtualWidth / 2, 0, NULL);
- LineTo(hMemoryDC, virtualWidth / 2, virtualHeight);
- MoveToEx(hMemoryDC, 0, virtualHeight / 2, NULL);
- LineTo(hMemoryDC, virtualWidth, virtualHeight / 2);
- DeleteObject(guidePen);
- // Draw canvas border
- HPEN edgePen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
- SelectObject(hMemoryDC, edgePen);
- Rectangle(hMemoryDC, 0, 0, virtualWidth - 1, virtualHeight - 1);
- DeleteObject(edgePen);
- // Draw all strokes
- std::lock_guard<std::mutex> lock(strokeMutex);
- for (const auto& stroke : strokeHistory) {
- if (stroke.points.empty()) continue; // Skip empty strokes
- // Handle single-point strokes
- if (stroke.points.size() == 1) {
- const DrawPoint& pt = stroke.points[0];
- COLORREF color = stroke.isEraser ? RGB(255, 255, 255) : stroke.color;
- HBRUSH brush = CreateSolidBrush(color);
- HPEN pen = CreatePen(PS_SOLID, 1, color);
- HBRUSH oldBrush = (HBRUSH)SelectObject(hMemoryDC, brush);
- HPEN oldPen = (HPEN)SelectObject(hMemoryDC, pen);
- Ellipse(hMemoryDC,
- pt.x - stroke.brushSize,
- pt.y - stroke.brushSize,
- pt.x + stroke.brushSize,
- pt.y + stroke.brushSize);
- SelectObject(hMemoryDC, oldBrush);
- SelectObject(hMemoryDC, oldPen);
- DeleteObject(brush);
- DeleteObject(pen);
- }
- // Handle multi-point strokes
- else {
- DrawSmoothStroke(hMemoryDC, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize);
- }
- }
- }
- void CompressStroke(CompressedStroke& stroke, double tolerance) {
- if (stroke.points.size() < 3) return;
- std::vector<DrawPoint> compressed;
- compressed.push_back(stroke.points.front());
- for (size_t i = 1; i < stroke.points.size() - 1; ++i) {
- const DrawPoint& prev = stroke.points[i - 1];
- const DrawPoint& curr = stroke.points[i];
- const DrawPoint& next = stroke.points[i + 1];
- double dx = next.x - prev.x;
- double dy = next.y - prev.y;
- double length = sqrt(dx * dx + dy * dy);
- if (length > tolerance) {
- compressed.push_back(curr);
- }
- }
- compressed.push_back(stroke.points.back());
- stroke.points = std::move(compressed);
- stroke.isCompressed = true;
- }
- void SaveCanvasState() {
- std::ofstream file(STATE_FILE, std::ios::binary | std::ios::out);
- if (!file) return;
- file.write(reinterpret_cast<const char*>(&gridZoomFactor), sizeof(float));
- file.write(reinterpret_cast<const char*>(&showGrid), sizeof(bool));
- file.write(reinterpret_cast<const char*>(&useAlphaGrid), sizeof(bool));
- file.write(reinterpret_cast<const char*>(&gridOpacity), sizeof(int));
- file.write(reinterpret_cast<const char*>(¤tBrushColor), sizeof(COLORREF));
- file.write(reinterpret_cast<const char*>(&brushSize), sizeof(int));
- std::lock_guard<std::mutex> lock(strokeMutex);
- size_t strokeCount = strokeHistory.size();
- file.write(reinterpret_cast<const char*>(&strokeCount), sizeof(size_t));
- for (const auto& stroke : strokeHistory) {
- // Ensure minimum point spacing
- std::vector<DrawPoint> optimizedPoints;
- if (!stroke.points.empty()) {
- optimizedPoints.push_back(stroke.points[0]);
- for (size_t i = 1; i < stroke.points.size(); ++i) {
- const DrawPoint& prev = optimizedPoints.back();
- const DrawPoint& curr = stroke.points[i];
- double dx = curr.x - prev.x;
- double dy = curr.y - prev.y;
- double distance = sqrt(dx * dx + dy * dy);
- if (distance >= MIN_DISTANCE) {
- optimizedPoints.push_back(curr);
- }
- }
- }
- size_t pointCount = optimizedPoints.size();
- file.write(reinterpret_cast<const char*>(&pointCount), sizeof(size_t));
- file.write(reinterpret_cast<const char*>(optimizedPoints.data()), pointCount * sizeof(DrawPoint));
- file.write(reinterpret_cast<const char*>(&stroke.color), sizeof(COLORREF));
- file.write(reinterpret_cast<const char*>(&stroke.brushSize), sizeof(int));
- file.write(reinterpret_cast<const char*>(&stroke.isEraser), sizeof(bool));
- }
- file.close();
- }
- void ProcessBatch(HDC hdc) {
- if (renderBatch.empty()) return;
- for (const auto& item : renderBatch) {
- DrawSmoothStroke(hdc, item.points, item.isEraser, item.color, item.brushSize);
- }
- renderBatch.clear();
- }
- void AddToBatch(const std::vector<DrawPoint>& points, COLORREF color, int brushSize, bool isEraser) {
- BatchRenderItem item;
- item.points = points;
- item.color = color;
- item.brushSize = brushSize;
- item.isEraser = isEraser;
- renderBatch.push_back(item);
- if (renderBatch.size() >= BATCH_SIZE) {
- ProcessBatch(hMemoryDC);
- }
- }
- bool InitializeMemoryMappedFile() {
- hMapFile = CreateFileMapping(
- INVALID_HANDLE_VALUE,
- NULL,
- PAGE_READWRITE,
- 0,
- MAP_SIZE,
- L"InfiniteCanvasMapping"
- );
- if (hMapFile == NULL) return false;
- pMapView = MapViewOfFile(
- hMapFile,
- FILE_MAP_ALL_ACCESS,
- 0,
- 0,
- MAP_SIZE
- );
- return (pMapView != NULL);
- }
- void CleanupMemoryMappedFile() {
- if (pMapView) {
- UnmapViewOfFile(pMapView);
- pMapView = NULL;
- }
- if (hMapFile) {
- CloseHandle(hMapFile);
- hMapFile = NULL;
- }
- }
- void RenderStrokeSegment(HDC targetDC, const std::vector<DrawPoint>& points, bool isEraser, COLORREF color, int size, int offsetX = 0, int offsetY = 0) {
- if (points.size() < 2) return;
- std::vector<DrawPoint> adjustedPoints;
- for (const auto& pt : points) {
- adjustedPoints.push_back(DrawPoint(pt.x - offsetX, pt.y - offsetY));
- }
- DrawSmoothStroke(targetDC, adjustedPoints, isEraser, color, size);
- }
- // Add these helper functions before your WindowProc
- inline int MinInt(int a, int b) { return (a < b) ? a : b; }
- inline int MaxInt(int a, int b) { return (a > b) ? a : b; }
- LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
- switch (uMsg) {
- case WM_CREATE:
- {
- if (!InitializeMemoryMappedFile()) {
- MessageBox(hwnd, L"Failed to initialize memory mapping", L"Error", MB_OK);
- return -1;
- }
- InitializeMemoryBitmap(hwnd);
- if (hMemoryDC) {
- HBRUSH redBrush = CreateSolidBrush(RGB(255, 0, 0));
- RECT testRect = { 100, 100, 200, 200 };
- FillRect(hMemoryDC, &testRect, redBrush);
- DeleteObject(redBrush);
- InvalidateRect(hwnd, NULL, FALSE);
- }
- else {
- MessageBox(hwnd, L"Memory DC failed to initialize", L"Error", MB_OK);
- }
- // Create status bar
- HWND hStatus = CreateWindowEx(
- 0,
- STATUSCLASSNAME,
- NULL,
- WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
- 0, 0, 0, 0,
- hwnd,
- (HMENU)0,
- hInst,
- NULL
- );
- if (hStatus) {
- int statwidths[] = { -1 };
- SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)statwidths);
- UpdateStatus(hwnd);
- }
- isLoading = true;
- LoadCanvasStateAsync(hwnd);
- return 0;
- }
- case WM_SYSCOMMAND:
- {
- if (wParam == SC_CLOSE) {
- DestroyWindow(hwnd);
- return 0;
- }
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- case WM_KEYDOWN:
- {
- if (GetKeyState(VK_MENU) & 0x8000) {
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- if (wParam == VK_SPACE && !isSpacePressed) {
- isSpacePressed = true;
- GetCursorPos(&lastMousePos);
- ScreenToClient(hwnd, &lastMousePos);
- SetCursor(LoadCursor(NULL, IDC_SIZEALL));
- SetCapture(hwnd);
- return 0;
- }
- else if (wParam == 0x50) {
- isPaintbrushSelected = true;
- isEraserMode = false;
- UpdateStatus(hwnd);
- }
- else if (wParam == 0x45) {
- isPaintbrushSelected = false;
- isEraserMode = true;
- UpdateStatus(hwnd);
- }
- else if (wParam == 'Q') {
- ShowColorPicker(hwnd);
- }
- else if (wParam == VK_ADD || wParam == VK_OEM_PLUS) {
- brushSize = std::min(50, brushSize + 5);
- UpdateStatus(hwnd);
- }
- else if (wParam == VK_SUBTRACT || wParam == VK_OEM_MINUS) {
- brushSize = std::max(5, brushSize - 5);
- UpdateStatus(hwnd);
- }
- else if (wParam == 0x43) {
- if (!(GetKeyState(VK_CONTROL) & 0x8000)) {
- ClearDrawing(hwnd);
- }
- }
- else if (wParam == VK_HOME) {
- RECT clientRect;
- GetClientRect(hwnd, &clientRect);
- scrollX = (virtualWidth - clientRect.right) / 2;
- scrollY = (virtualHeight - clientRect.bottom) / 2;
- InvalidateRect(hwnd, NULL, FALSE);
- }
- else if (wParam == 'G') {
- showGrid = !showGrid;
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- else if (wParam == 'A') {
- useAlphaGrid = !useAlphaGrid;
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- if (wParam == VK_PRIOR) {
- gridZoomFactor *= 1.1f;
- gridInitialized = FALSE;
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- else if (wParam == VK_NEXT) {
- gridZoomFactor *= 0.9f;
- if (gridZoomFactor < 0.1f) gridZoomFactor = 0.1f;
- gridInitialized = FALSE;
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- else if (wParam == VK_OEM_6 && useAlphaGrid) {
- gridOpacity = min(255, gridOpacity + 15);
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- else if (wParam == VK_OEM_4 && useAlphaGrid) {
- gridOpacity = max(0, gridOpacity - 15);
- InvalidateRect(hwnd, NULL, FALSE);
- UpdateStatus(hwnd);
- }
- else if (wParam == VK_ESCAPE) {
- ProcessBatch(hMemoryDC); // Process any remaining batched strokes
- CleanupMemoryMappedFile();
- //if (!strokeBuffer.empty()) { // Only save if there are points
- //SaveCanvasState(); // Save canvas state before exiting
- //}
- PostQuitMessage(0);
- return 0;
- }
- else if (wParam == VK_F1) {
- 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);
- }
- return 0;
- }
- case WM_KEYUP:
- {
- if (wParam == VK_SPACE) {
- isSpacePressed = false;
- SetCursor(LoadCursor(NULL, IDC_ARROW));
- ReleaseCapture();
- return 0;
- }
- }
- case WM_LBUTTONDOWN:
- {
- if (!isSpacePressed) {
- isDrawing = true;
- SetCapture(hwnd);
- // Extract x and y from lParam
- int x = GET_X_LPARAM(lParam);
- int y = GET_Y_LPARAM(lParam);
- int canvasX = x + scrollX;
- int canvasY = y + scrollY;
- if (canvasX >= 0 && canvasX <= virtualWidth &&
- canvasY >= 0 && canvasY <= virtualHeight) {
- StartStroke(canvasX, canvasY);
- // Draw initial point
- DrawBrush(hMemoryDC, canvasX, canvasY, isEraserMode, false);
- // Draw to screen
- HDC screenDC = GetDC(hwnd);
- if (screenDC) {
- DrawBrush(screenDC, x, y, isEraserMode, false);
- ReleaseDC(hwnd, screenDC);
- }
- InvalidateRect(hwnd, NULL, FALSE);
- }
- }
- return 0;
- }
- // In WM_LBUTTONUP case:
- case WM_LBUTTONUP:
- {
- if (isDrawing) {
- isDrawing = false;
- SerializedStroke stroke;
- stroke.points = strokeBuffer; // Even if it's just one point
- stroke.color = currentBrushColor;
- stroke.brushSize = brushSize;
- stroke.isEraser = isEraserMode;
- {
- std::lock_guard<std::mutex> lock(strokeMutex);
- strokeHistory.push_back(stroke);
- }
- SaveCanvasState();
- EndStroke();
- ReleaseCapture();
- InvalidateRect(hwnd, NULL, FALSE);
- }
- return 0;
- }
- case WM_MOUSEMOVE:
- {
- int x = LOWORD(lParam);
- int y = HIWORD(lParam);
- if (isSpacePressed) {
- RECT clientRect;
- GetClientRect(hwnd, &clientRect);
- int deltaX = x - lastMousePos.x;
- int deltaY = y - lastMousePos.y;
- // Calculate new scroll positions with bounds checking
- int newScrollX = scrollX - deltaX;
- int newScrollY = scrollY - deltaY;
- // Clamp scroll values using std::max and std::min
- //newScrollX = std::max(0, std::min(virtualWidth - clientRect.right, newScrollX));
- newScrollX = MaxInt(0, MinInt(virtualWidth - clientRect.right, newScrollX));
- //newScrollY = std::max(0, std::min(virtualHeight - clientRect.bottom, newScrollY));
- newScrollY = MaxInt(0, MinInt(virtualHeight - clientRect.bottom, newScrollY));
- if (newScrollX != scrollX || newScrollY != scrollY) {
- scrollX = newScrollX;
- scrollY = newScrollY;
- ScrollWindowEx(hwnd, deltaX, deltaY,
- NULL, NULL, NULL, NULL,
- SW_INVALIDATE | SW_ERASE);
- // Update status less frequently during panning
- if (std::abs(deltaX) > 5 || std::abs(deltaY) > 5) {
- UpdateStatus(hwnd);
- }
- }
- lastMousePos.x = x;
- lastMousePos.y = y;
- }
- else if (isDrawing && (wParam & MK_LBUTTON)) {
- int canvasX = x + scrollX;
- int canvasY = y + scrollY;
- if (canvasX >= 0 && canvasX <= virtualWidth &&
- canvasY >= 0 && canvasY <= virtualHeight) {
- AddToStroke(canvasX, canvasY); // Use the stroke buffer system
- // Draw the current segment to both memory DC and screen
- if (strokeBuffer.size() >= 2) {
- std::vector<DrawPoint> currentSegment;
- currentSegment.push_back(strokeBuffer[strokeBuffer.size() - 2]);
- currentSegment.push_back(strokeBuffer[strokeBuffer.size() - 1]);
- // Draw to memory DC (permanent canvas)
- DrawSmoothStroke(hMemoryDC, currentSegment, isEraserMode, currentBrushColor, brushSize);
- // Draw to screen (temporary view)
- HDC screenDC = GetDC(hwnd);
- if (screenDC) {
- std::vector<DrawPoint> screenPoints;
- for (const auto& pt : currentSegment) {
- screenPoints.push_back(DrawPoint(
- pt.x - scrollX,
- pt.y - scrollY
- ));
- }
- DrawSmoothStroke(screenDC, screenPoints, isEraserMode, currentBrushColor, brushSize);
- ReleaseDC(hwnd, screenDC);
- }
- }
- DWORD currentTime = GetTickCount();
- if (currentTime - lastStatusUpdateTime > STATUS_UPDATE_INTERVAL) {
- UpdateStatus(hwnd);
- lastStatusUpdateTime = currentTime;
- }
- }
- }
- return 0;
- }
- case WM_SIZE:
- {
- HWND hStatus = GetDlgItem(hwnd, 0);
- if (hStatus) {
- SendMessage(hStatus, WM_SIZE, 0, 0);
- InitializeStatusBuffer(hStatus);
- UpdateStatus(hwnd);
- }
- if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) {
- InvalidateRect(hwnd, NULL, TRUE);
- }
- return 0;
- }
- case WM_PAINT:
- {
- PAINTSTRUCT ps;
- HDC hdc = BeginPaint(hwnd, &ps);
- RECT clientRect;
- GetClientRect(hwnd, &clientRect);
- int windowWidth = clientRect.right - clientRect.left;
- int windowHeight = clientRect.bottom - clientRect.top;
- HDC memDC = CreateCompatibleDC(hdc);
- HBITMAP memBitmap = CreateCompatibleBitmap(hdc, windowWidth, windowHeight);
- HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
- HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
- FillRect(memDC, &clientRect, whiteBrush);
- DeleteObject(whiteBrush);
- BitBlt(memDC, 0, 0, windowWidth, windowHeight, hMemoryDC, scrollX, scrollY, SRCCOPY);
- if (showGrid) {
- if (useAlphaGrid) {
- DrawGridWithAlpha(memDC, clientRect, scrollX, scrollY);
- }
- else {
- DrawGrid(memDC, clientRect, scrollX, scrollY);
- }
- }
- BitBlt(hdc, 0, 0, windowWidth, windowHeight, memDC, 0, 0, SRCCOPY);
- SelectObject(memDC, oldBitmap);
- DeleteObject(memBitmap);
- DeleteDC(memDC);
- EndPaint(hwnd, &ps);
- return 0;
- }
- case WM_SETCURSOR:
- {
- if (LOWORD(lParam) == HTCLIENT) {
- if (isSpacePressed) {
- SetCursor(LoadCursor(NULL, IDC_SIZEALL));
- return TRUE;
- }
- else if (isPaintbrushSelected || isEraserMode) {
- SetCursor(LoadCursor(NULL, IDC_CROSS));
- return TRUE;
- }
- }
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- case WM_MOUSEWHEEL:
- {
- int delta = GET_WHEEL_DELTA_WPARAM(wParam);
- if (GetKeyState(VK_CONTROL) & 0x8000) {
- if (delta > 0)
- gridZoomFactor *= 1.1f;
- else
- gridZoomFactor *= 0.9f;
- InvalidateRect(hwnd, NULL, FALSE);
- }
- return 0;
- }
- case WM_DESTROY:
- {
- ProcessBatch(hMemoryDC); // Process any remaining batched strokes
- CleanupMemoryMappedFile();
- // Save the canvas state before exiting
- //if (!strokeHistory.empty()) {
- //SaveCanvasState();
- //}
- // Clean up resources
- if (hGridDC) {
- DeleteDC(hGridDC);
- hGridDC = NULL;
- }
- if (hGridBitmap) {
- DeleteObject(hGridBitmap);
- hGridBitmap = NULL;
- }
- if (hMemoryDC) {
- if (hOldBitmap) {
- SelectObject(hMemoryDC, hOldBitmap);
- hOldBitmap = NULL;
- }
- DeleteDC(hMemoryDC);
- hMemoryDC = NULL;
- }
- if (hMemoryBitmap) {
- DeleteObject(hMemoryBitmap);
- hMemoryBitmap = NULL;
- }
- if (hStatusBufferDC) {
- DeleteDC(hStatusBufferDC);
- hStatusBufferDC = NULL;
- }
- if (hStatusBufferBitmap) {
- DeleteObject(hStatusBufferBitmap);
- hStatusBufferBitmap = NULL;
- }
- PostQuitMessage(0);
- return 0;
- }
- default:
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- return 0;
- }
- INITCOMMONCONTROLSEX icex;
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
- icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
- icex.dwICC = ICC_BAR_CLASSES;
- InitCommonControlsEx(&icex);
- const wchar_t CLASS_NAME[] = L"DoodleAppClass";
- WNDCLASS wc = { };
- wc.lpfnWndProc = WindowProc;
- wc.hInstance = hInstance;
- wc.lpszClassName = CLASS_NAME;
- wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
- RegisterClass(&wc);
- hInst = hInstance;
- HWND hwnd = CreateWindowEx(
- 0,
- CLASS_NAME,
- 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)",
- WS_OVERLAPPEDWINDOW | WS_MAXIMIZE,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- NULL,
- NULL,
- hInstance,
- NULL
- );
- SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_COMPOSITED);
- if (hwnd == NULL) {
- return 0;
- }
- HWND hStatus = CreateWindowEx(
- 0,
- STATUSCLASSNAME,
- NULL,
- WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
- 0, 0, 0, 0,
- hwnd,
- (HMENU)0,
- hInstance,
- NULL
- );
- if (hStatus) {
- int statwidths[] = { -1 };
- SendMessage(hStatus, SB_SETPARTS, 1, (LPARAM)statwidths);
- UpdateStatus(hwnd);
- }
- ShowWindow(hwnd, SW_SHOWMAXIMIZED);
- MSG msg = {};
- while (GetMessage(&msg, NULL, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return 0;
- }
Add Comment
Please, Sign In to add comment