diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1b2eb66 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.29) +project(cg1) + +set(CMAKE_CXX_STANDARD 20) + + +add_executable( + cg1 + + WIN32 + + src/Frame.h + src/main.cpp + src/Painter.h +) \ No newline at end of file diff --git a/src/Frame.h b/src/Frame.h new file mode 100644 index 0000000..f8a09c2 --- /dev/null +++ b/src/Frame.h @@ -0,0 +1,164 @@ +#ifndef FRAME_H +#define FRAME_H + +#include + +// Cтруктура для задания цвета +typedef struct tagCOLOR +{ + unsigned char RED; // Компонента красного цвета + unsigned char GREEN; // Компонента зелёного цвета + unsigned char BLUE; // Компонента синего цвета + unsigned char ALPHA; // Прозрачность (альфа канал) + + tagCOLOR() : RED(0), GREEN(0), BLUE(0), ALPHA(255) { } + tagCOLOR(unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha = 255) : RED(red), GREEN(green), BLUE(blue), ALPHA(alpha) { } + +} COLOR; + + +template void swap(TYPE& a, TYPE& b) +{ + TYPE t = a; + a = b; + b = t; +} + + +// Буфер кадра +class Frame +{ + // Указатель на массив пикселей + // Буфер кадра будет представлять собой матрицу, которая располагается в памяти в виде непрерывного блока + COLOR* pixels; + + // Указатели на строки пикселей буфера кадра + COLOR** matrix; + +public: + + // Размеры буфера кадра + int width, height; + + Frame(int _width, int _height) : width(_width), height(_height) + { + int size = width * height; + + // Создание буфера кадра в виде непрерывной матрицы пикселей + pixels = new COLOR[size]; + + // Указатели на строки пикселей запишем в отдельный массив + matrix = new COLOR* [height]; + + // Инициализация массива указателей + for (int i = 0; i < height; i++) + { + matrix[i] = pixels + i * width; + } + } + + + // Задаёт цвет color пикселю с координатами (x, y) + void SetPixel(int x, int y, COLOR color) + { + matrix[y][x] = color; + } + + // Возвращает цвет пикселя с координатами (x, y) + COLOR GetPixel(int x, int y) + { + return matrix[y][x]; + } + + + // Рисование окружности + void Circle(int x0, int y0, int radius, COLOR color) + { + int x = 0, y = radius; + while(x < y) + { + // Определяем, какая точка (пиксель): (x, y) или (x, y - 1) ближе к линии окружности + int D1 = x * x + y * y - radius * radius; + int D2 = x * x + (y - 1) * (y - 1) - radius * radius; + + // Если ближе точка (x, y - 1), то смещаемся к ней + if (D1 > -D2) + y--; + + // Перенос и отражение вычисленных координат на все октанты окружности + SetPixel(x0 + x, y0 + y, color); + SetPixel(x0 + x, y0 - y, color); + SetPixel(x0 + y, y0 + x, color); + SetPixel(x0 + y, y0 - x, color); + SetPixel(x0 - x, y0 + y, color); + SetPixel(x0 - x, y0 - y, color); + SetPixel(x0 - y, y0 + x, color); + SetPixel(x0 - y, y0 - x, color); + x++; + } + } + + + // Рисование отрезка + void DrawLine(int x1, int y1, int x2, int y2, COLOR color) + { + int dy = y2 - y1, dx = x2 - x1; + if (dx == 0 && dy == 0) + { + matrix[y1][x1] = color; + return; + } + + if (abs(dx) > abs(dy)) + { + if (x2 < x1) + { + // Обмен местами точек (x1, y1) и (x2, y2) + swap(x1, x2); + swap(y1, y2); + dx = -dx; dy = -dy; + } + + int y, dx2 = dx / 2, p = 0; + if (dy < 0) dx2 = -dx2; + for (int x = x1; x <= x2; x++) + { + // y = (dy * (x - x1) + dx2) / dx + y1; + y = (p + dx2) / dx + y1; + p += dy; + matrix[y][x] = color; + } + } + else + { + if (y2 < y1) + { + // Обмен местами точек (x1, y1) и (x2, y2) + swap(x1, x2); + swap(y1, y2); + dx = -dx; dy = -dy; + } + + int x, dy2 = dy / 2, p = 0; + if (dx < 0) dy2 = -dy2; + for (int y = y1; y <= y2; y++) + { + // x = (dx * (y - y1) + dy2) / dy + x1; + x = (p + dy2) / dy + x1; + p += dx; + matrix[y][x] = color; + } + } + } + + + ~Frame(void) + { + delete []pixels; + delete []matrix; + } + +}; + + +#endif // FRAME_H diff --git a/src/Painter.h b/src/Painter.h new file mode 100644 index 0000000..8d187ac --- /dev/null +++ b/src/Painter.h @@ -0,0 +1,80 @@ +#ifndef PAINTER_H +#define PAINTER_H + +#include "Frame.h" + + +// Угол поворота фигуры +float global_angle = 0; + +// Координаты последнего пикселя, который выбрал пользователь +struct +{ + int X, Y; +} global_clicked_pixel = {-1, -1}; + + +class Painter +{ +public: + + void Draw(Frame& frame) + { + // Шахматная текстура + for (int y = 0; y < frame.height; y++) + for (int x = 0; x < frame.width; x++) + { + if ((x + y) % 2 == 0) + frame.SetPixel(x, y, { 230, 255, 230 }); // Золотистый цвет + //frame.SetPixel(x, y, { 217, 168, 14 }); + else + frame.SetPixel(x, y, { 200, 200, 200 }); // Чёрный цвет + //frame.SetPixel(x, y, { 255, 255, 255 }); // Белый цвет + } + + + int W = frame.width, H = frame.height; + // Размер рисунка возьмём меньше (7 / 8), чтобы он не касался границ экрана + float a = 7.0f / 8 * ((W < H) ? W - 1 : H - 1) / sqrt(2); + if (a < 1) return; // Если окно очень маленькое, то ничего не рисуем + float angle = global_angle; // Угол поворота + a = a / 2; + + // Инициализируем исходные координаты центра и вершин квадрата + struct + { + float x; + float y; + } C = {W / 2, H / 2}, A[4] = { { C.x + a, C.y + a}, {C.x + a, C.y - a}, {C.x - a, C.y - a}, {C.x - a, C.y + a} }; + + + // Поворачиваем все вершины квадрата вокруг точки C на угол angle + for (int i = 0; i < 4; i++) + { + float xi = A[i].x, yi = A[i].y; + A[i].x = (xi - C.x) * cos(angle) - (yi - C.y) * sin(angle) + C.x; + A[i].y = (xi - C.x) * sin(angle) + (yi - C.y) * cos(angle) + C.y; + } + + // Рисуем стороны квадрата + for (int i = 0; i < 4; i++) + { + int i2 = (i + 1) % 4; + frame.DrawLine( // Добавляем везде 0.5f, чтобы вещественные числа правильно округлялись при преобразовании к целому типу + int(A[i].x + 0.5f), + int(A[i].y + 0.5f), + int(A[i2].x + 0.5f), + int(A[i2].y + 0.5f), COLOR(200, 30, 45)); + } + + // Рисуем описанную окружность + frame.Circle((int)C.x, (int)C.y, int(a*sqrt(2) + 0.5f), COLOR(100, 100, 250)); + + // Рисуем пиксель, на который кликнул пользователь + if (global_clicked_pixel.X >= 0 && global_clicked_pixel.X < W && + global_clicked_pixel.Y >= 0 && global_clicked_pixel.Y < H) + frame.SetPixel(global_clicked_pixel.X, global_clicked_pixel.Y, { 34, 175, 60 }); // Пиксель зелёного цвета + } +}; + +#endif // PAINTER_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1f9d867 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,254 @@ +// lab_1_basics.cpp : Определяет точку входа для приложения. + + +#include +#include +#include "stdio.h" +#include "Frame.h" +#include "Painter.h" + +// Windows-приложение для создания буфера кадра + +LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM); + +HWND hWndStatusBar; // Дескриптор компонента StatusBar + +int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) +{ + char szWinName[] = "Graphics Window Class"; // Имя класса окна + + HWND hWnd; // Дескриптор главного окна + MSG msg; + WNDCLASSA wcl; // Определитель класса окна + wcl.hInstance = hThisInstance; // Дескриптор приложения + wcl.lpszClassName = szWinName;// Имя класса окна + wcl.lpfnWndProc = WindowProc; // Функция обработки сообщений + wcl.style = 0; // Стиль по умолчанию + wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION);// Иконка + wcl.hCursor = LoadCursor(NULL, IDC_ARROW); // Курсор + wcl.lpszMenuName = NULL; // Без меню + wcl.cbClsExtra = 0; // Без дополнительной информации + wcl.cbWndExtra = 0; + + wcl.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //Белый фон + + if (!RegisterClassA(&wcl)) // Регистрируем класс окна + return 0; + + hWnd = CreateWindowA(szWinName, // Создать окно + "Лабораторная работа №1. Буфер кадра. Алгоритмы Брезенхейма", + WS_OVERLAPPEDWINDOW, // Стиль окна + CW_USEDEFAULT, // x-координата + CW_USEDEFAULT, // y-координата + CW_USEDEFAULT, // Ширина + CW_USEDEFAULT, // Высота + HWND_DESKTOP, // Без родительского окна + NULL, // Без меню + hThisInstance, // Дескриптор приложения + NULL); // Без дополнительных аргументов + + // Создаём компонент типа StatusBar + hWndStatusBar = CreateWindowExA( + 0, STATUSCLASSNAMEA, NULL, + WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, + 0, 0, 0, 0, + hWnd, (HMENU) 10001, + hThisInstance, NULL + ); + + // Настройка частей StatusBar'а + int statwidths[] = { 150, 300, -1 }; + SendMessageA(hWndStatusBar, SB_SETPARTS, sizeof(statwidths) / sizeof(int), (LPARAM)statwidths); + + ShowWindow(hWnd, nWinMode); // Показать окно + UpdateWindow(hWnd); // Перерисовать окно + + while (GetMessage(&msg, NULL, 0, 0)) // Запустить цикл обработки сообщений + { + TranslateMessage(&msg); // Разрешить использование клавиатуры + DispatchMessage(&msg); // Вернуть управление операционной системе Windows + } + + return msg.wParam; + +} + +// Следующая функция вызывается операционной системой Windows и получает в качестве +// параметров сообщения из очереди сообщений данного приложения +LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + static int pixelSize = 8; // Размер "большого" пикселя + + switch (message) + { + // Обработка сообщения на создание окна + case WM_CREATE: + { + // Создаем таймер, посылающий сообщения + // функции окна примерно 30 раз в секунду + SetTimer(hWnd, 1, 1000/30, NULL); + } + break; + + + // Обработка сообщения на перерисовку окна + case WM_PAINT: + { + PAINTSTRUCT ps; + + HDC hdc = BeginPaint(hWnd, &ps); + + // Определяем ширину и высоту окна + RECT rect = ps.rcPaint; + GetClientRect(hWnd, &rect); + + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + // Рисование в буфер кадра + + int ratio = pixelSize; // Размер "большого" пикселя + + int W = width / ratio; + int H = (height - 22) / ratio; // Отнимем высоту StatusBar'а + + Frame frame(W, H); + Painter painter; + painter.Draw(frame); + + // Системная структура для хранения цвета пикселя + // Буфер кадра, который будет передаваться операционной системе, должен состоять из массива этих структур + // Она не совпадает с порядком следования цветов в формате RBG + typedef struct tagRGBPIXEL + { + unsigned char BLUE; // Компонента синего цвета + unsigned char GREEN; // Компонента зелёного цвета + unsigned char RED; // Компонента красного цвета + unsigned char ALPHA; // Прозрачность + } RGBPIXEL; + + // Выделение памяти для второго буфера, который будет передаваться функции CreateBitmap для создания картинки + RGBPIXEL* bitmap = (RGBPIXEL*) HeapAlloc(GetProcessHeap(), 0, width * height * sizeof(RGBPIXEL)); + + // Копирование массива пикселей в соответствии с системным форматом пикселя и масштабирование картинки + // W и H - ширина и высота изображения в буфере кадра + // ratio - коэффициент масштабирования пикселей + for (int y = 0; y < H * ratio; y++) + for (int x = 0; x < W * ratio; x++) + { + RGBPIXEL* pixel = bitmap + y * width + x; + COLOR color = frame.GetPixel(x / ratio, y / ratio); + pixel->RED = color.RED; + pixel->GREEN = color.GREEN; + pixel->BLUE = color.BLUE; + pixel->ALPHA = color.ALPHA; + } + + + // Получить дескриптор на новое растровое изображение + HBITMAP hBitMap = CreateBitmap(width, height, 1, sizeof(RGBPIXEL) * 8, bitmap); + + // Освободить память, которую занимает буфер цвета + HeapFree(GetProcessHeap(), 0, bitmap); + + // Создать в оперативной памяти контекст, совместимый с экранным контекстом, который мы используем, чтобы рисовать + HDC srcHdc = CreateCompatibleDC(hdc); + + // Связать картинку с новым контекстом + SelectObject(srcHdc, hBitMap); + + // Копировать содержимое из временного контекста srcHdc в основной контекст окна hdc + BitBlt( + hdc, // Основной контекст + 0, 0, // Координаты левого верхнего угла, от которого будет выполняться вставка + width, // Ширина вставляемого изображения + height, // Высота вставляемого изображения + srcHdc, // Дескриптор временного контекста + 0, 0, // Координаты считываемого изображения + SRCCOPY); // Параметры операции - копирование + + EndPaint(hWnd, &ps); + + // Удаление картинки из памяти + DeleteObject(hBitMap); + + // Удаление временного контекста + DeleteDC(srcHdc); + } + break; + + case WM_MOUSEMOVE: + { + char str[256]; + + // Устанавливаем текст в разных частях StatusBar'а + // Экранные координаты курсора мыши + sprintf_s(str, "X = %d, Y = %d", LOWORD(lParam), HIWORD(lParam)); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 2, (LPARAM)str); + + // Координаты пикселя в буфере кадра + sprintf_s(str, "BX = %d, BY = %d", LOWORD(lParam) / pixelSize, HIWORD(lParam) / pixelSize); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 1, (LPARAM)str); + + sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM)str); + } + break; + + case WM_LBUTTONDOWN: + // Запоминаем координаты пикселя, по которому щёлкнул пользователь + global_clicked_pixel.X = LOWORD(lParam) / pixelSize; + global_clicked_pixel.Y = HIWORD(lParam) / pixelSize; + // Перерисовать окно + InvalidateRect(hWnd, NULL, false); + break; + + case WM_KEYDOWN: + if (wParam == VK_F2 || wParam == VK_F3) + { + if (pixelSize > 1 && wParam == VK_F2) pixelSize--; + if (pixelSize < 64 && wParam == VK_F3) pixelSize++; + + // Перерисовать окно + InvalidateRect(hWnd, NULL, false); + + char str[256]; + sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM)str); + } + if (wParam == VK_F1) + { + MessageBoxA(hWnd, "Работу выполнил студент группы ПВ-221 Колесников А.И.", "О программе", MB_ICONINFORMATION); + } + break; + + // Обработка сообщения на изменение размера окна + case WM_SIZE: + + // Подгоняем размеры StatusBar под размер окна + SendMessageA(hWndStatusBar, WM_SIZE, 0, 0); + + // Перерисовать окно + InvalidateRect(hWnd, NULL, false); + break; + + case WM_TIMER: + + // При срабатывании таймера увеличим угол поворота + global_angle += 0.05; + // Перерисовать окно + InvalidateRect(hWnd, NULL, false); + break; + + case WM_DESTROY: // Завершение программы + PostQuitMessage(0); + break; + + default: + // Все сообщения, не обрабатываемые в данной функции, направляются на обработку по умолчанию + return DefWindowProcA(hWnd, message, wParam, lParam); +} + + return 0; +} +