diff --git a/CMakeLists.txt b/CMakeLists.txt index e4d5a07..1912f82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,9 @@ add_executable( WIN32 - src/Frame.h + src/frame.hpp src/main.cpp - src/Painter.h + src/painter.cpp + src/painter.hpp src/shaders.cpp ) \ No newline at end of file diff --git a/src/Frame.h b/src/Frame.h deleted file mode 100644 index f8a09c2..0000000 --- a/src/Frame.h +++ /dev/null @@ -1,164 +0,0 @@ -#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 deleted file mode 100644 index 8d187ac..0000000 --- a/src/Painter.h +++ /dev/null @@ -1,80 +0,0 @@ -#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/frame.hpp b/src/frame.hpp new file mode 100644 index 0000000..1a0390b --- /dev/null +++ b/src/frame.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include + +struct COLOR { + unsigned char red; + unsigned char green; + unsigned char blue; + unsigned char alpha; + + COLOR() : red(0), green(0), blue(0), alpha(255) {} + + COLOR(unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha = 255) : red(red), green(green), blue(blue), alpha(alpha) {} + +} ; + + +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; + } + } + + + void SetPixel(int x, int y, COLOR color) { + matrix[y][x] = color; + } + + 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) { + 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 = (p + dx2) / dx + y1; + p += dy; + matrix[y][x] = color; + } + } else { + if (y2 < y1) { + 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 = (p + dy2) / dy + x1; + p += dx; + matrix[y][x] = color; + } + } + } + + + ~Frame() { + delete[]pixels; + delete[]matrix; + } + +}; diff --git a/src/main.cpp b/src/main.cpp index 1f9d867..dfdce00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,8 +4,8 @@ #include #include #include "stdio.h" -#include "Frame.h" -#include "Painter.h" +#include "frame.hpp" +#include "painter.hpp" // Windows-приложение для создания буфера кадра @@ -13,8 +13,7 @@ LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM); HWND hWndStatusBar; // Дескриптор компонента StatusBar -int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) -{ +int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { char szWinName[] = "Graphics Window Class"; // Имя класса окна HWND hWnd; // Дескриптор главного окна @@ -24,47 +23,49 @@ int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, 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.hIcon = LoadIcon(nullptr, IDI_APPLICATION);// Иконка + wcl.hCursor = LoadCursor(nullptr, IDC_ARROW); // Курсор + wcl.lpszMenuName = nullptr; // Без меню wcl.cbClsExtra = 0; // Без дополнительной информации - wcl.cbWndExtra = 0; + wcl.cbWndExtra = sizeof(Painter *); + static_assert(sizeof(Painter *) == sizeof(LONG_PTR)); - wcl.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //Белый фон + + 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); // Без дополнительных аргументов + "Лабораторная работа №1. Буфер кадра. Алгоритмы Брезенхейма", + WS_OVERLAPPEDWINDOW, // Стиль окна + CW_USEDEFAULT, // x-координата + CW_USEDEFAULT, // y-координата + CW_USEDEFAULT, // Ширина + CW_USEDEFAULT, // Высота + HWND_DESKTOP, // Без родительского окна + nullptr, // Без меню + hThisInstance, // Дескриптор приложения + nullptr); // Без дополнительных аргументов // Создаём компонент типа StatusBar hWndStatusBar = CreateWindowExA( - 0, STATUSCLASSNAMEA, NULL, - WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, - 0, 0, 0, 0, - hWnd, (HMENU) 10001, - hThisInstance, NULL + 0, STATUSCLASSNAMEA, nullptr, + WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, + 0, 0, 0, 0, + hWnd, (HMENU) 10001, + hThisInstance, nullptr ); // Настройка частей StatusBar'а - int statwidths[] = { 150, 300, -1 }; - SendMessageA(hWndStatusBar, SB_SETPARTS, sizeof(statwidths) / sizeof(int), (LPARAM)statwidths); + 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)) // Запустить цикл обработки сообщений - { + while (GetMessage(&msg, nullptr, 0, 0)) // Запустить цикл обработки сообщений + { TranslateMessage(&msg); // Разрешить использование клавиатуры DispatchMessage(&msg); // Вернуть управление операционной системе Windows } @@ -73,27 +74,23 @@ int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, } +static Painter painter; + // Следующая функция вызывается операционной системой Windows и получает в качестве // параметров сообщения из очереди сообщений данного приложения -LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -{ +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); + switch (message) { + case WM_CREATE: { + SetTimer(hWnd, 1, 1000 / 30, nullptr); + SetWindowLongPtr(hWnd, 0, reinterpret_cast(new Painter{})); } - break; + break; - // Обработка сообщения на перерисовку окна - case WM_PAINT: - { + // Обработка сообщения на перерисовку окна + case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); @@ -101,7 +98,7 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara // Определяем ширину и высоту окна RECT rect = ps.rcPaint; GetClientRect(hWnd, &rect); - + int width = rect.right - rect.left; int height = rect.bottom - rect.top; @@ -113,35 +110,33 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara int H = (height - 22) / ratio; // Отнимем высоту StatusBar'а Frame frame(W, H); - Painter painter; - painter.Draw(frame); + auto *painter = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + painter->Draw(frame); // Системная структура для хранения цвета пикселя // Буфер кадра, который будет передаваться операционной системе, должен состоять из массива этих структур // Она не совпадает с порядком следования цветов в формате RBG - typedef struct tagRGBPIXEL - { - unsigned char BLUE; // Компонента синего цвета - unsigned char GREEN; // Компонента зелёного цвета - unsigned char RED; // Компонента красного цвета + 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)); + 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; + 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; + pixel->RED = color.red; + pixel->GREEN = color.green; + pixel->BLUE = color.blue; + pixel->ALPHA = color.alpha; } @@ -159,13 +154,13 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara // Копировать содержимое из временного контекста srcHdc в основной контекст окна hdc BitBlt( - hdc, // Основной контекст - 0, 0, // Координаты левого верхнего угла, от которого будет выполняться вставка - width, // Ширина вставляемого изображения - height, // Высота вставляемого изображения - srcHdc, // Дескриптор временного контекста - 0, 0, // Координаты считываемого изображения - SRCCOPY); // Параметры операции - копирование + hdc, // Основной контекст + 0, 0, // Координаты левого верхнего угла, от которого будет выполняться вставка + width, // Ширина вставляемого изображения + height, // Высота вставляемого изображения + srcHdc, // Дескриптор временного контекста + 0, 0, // Координаты считываемого изображения + SRCCOPY); // Параметры операции - копирование EndPaint(hWnd, &ps); @@ -175,79 +170,68 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara // Удаление временного контекста DeleteDC(srcHdc); } - break; + break; - case WM_MOUSEMOVE: - { + 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); + 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); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 1, (LPARAM) str); sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); - SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM)str); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM) str); } - break; + break; case WM_LBUTTONDOWN: - // Запоминаем координаты пикселя, по которому щёлкнул пользователь - global_clicked_pixel.X = LOWORD(lParam) / pixelSize; - global_clicked_pixel.Y = HIWORD(lParam) / pixelSize; - // Перерисовать окно + reinterpret_cast(GetWindowLongPtr(hWnd, 0))->SetClickedPixel(LOWORD(lParam) / pixelSize, 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 (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); + SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM) str); } - if (wParam == VK_F1) - { + if (wParam == VK_F1) { MessageBoxA(hWnd, "Работу выполнил студент группы ПВ-221 Колесников А.И.", "О программе", MB_ICONINFORMATION); } break; - // Обработка сообщения на изменение размера окна + // Обработка сообщения на изменение размера окна case WM_SIZE: - - // Подгоняем размеры StatusBar под размер окна SendMessageA(hWndStatusBar, WM_SIZE, 0, 0); - - // Перерисовать окно - InvalidateRect(hWnd, NULL, false); + InvalidateRect(hWnd, nullptr, false); break; case WM_TIMER: - - // При срабатывании таймера увеличим угол поворота - global_angle += 0.05; - // Перерисовать окно - InvalidateRect(hWnd, NULL, false); + reinterpret_cast(GetWindowLongPtr(hWnd, 0))->AngleStep(); + InvalidateRect(hWnd, nullptr, false); break; - case WM_DESTROY: // Завершение программы + + case WM_DESTROY: + delete reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + SetWindowLongPtr(hWnd, 0, reinterpret_cast(nullptr)); PostQuitMessage(0); break; default: // Все сообщения, не обрабатываемые в данной функции, направляются на обработку по умолчанию return DefWindowProcA(hWnd, message, wParam, lParam); -} + } return 0; } diff --git a/src/painter.cpp b/src/painter.cpp new file mode 100644 index 0000000..087379a --- /dev/null +++ b/src/painter.cpp @@ -0,0 +1,59 @@ +#include "frame.hpp" +#include "painter.hpp" + + +void Painter::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 = this->global_angle; // Угол поворота + a = a / 2; + + // Инициализируем исходные координаты центра и вершин квадрата + struct { + float x; + float y; + } C = {(float) W / 2, (float) 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}); // Пиксель зелёного цвета +} diff --git a/src/painter.hpp b/src/painter.hpp new file mode 100644 index 0000000..9e52535 --- /dev/null +++ b/src/painter.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "frame.hpp" + + +class Painter { +private: + float global_angle = 0; + + struct { + int X, Y; + } global_clicked_pixel = {-1, -1}; +public: + + + void SetClickedPixel(int x, int y) { + this->global_clicked_pixel.X = x; + this->global_clicked_pixel.Y = y; + } + + void AngleStep() { + this->global_angle += 0.05; + } + + + void Draw(Frame &frame); +}; diff --git a/src/shaders.cpp b/src/shaders.cpp index b767303..0cf0ee7 100644 --- a/src/shaders.cpp +++ b/src/shaders.cpp @@ -1,21 +1,18 @@ -#include "Painter.h" +#include "frame.hpp" +#include "painter.hpp" -template -void Triangle(float x0, float y0, float x1, float y1, float x2, float y2, COLOR color, ShaderClass &shader) -{ +template +void Triangle(Frame *f, float x0, float y0, float x1, float y1, float x2, float y2, COLOR color, ShaderClass *shader) { // Отсортируем точки таким образом, чтобы выполнилось условие: y0 < y1 < y2 - if (y1 < y0) - { + if (y1 < y0) { swap(y1, y0); swap(x1, x0); } - if (y2 < y1) - { + if (y2 < y1) { swap(y2, y1); swap(x2, x1); } - if (y1 < y0) - { + if (y1 < y0) { swap(y1, y0); swap(x1, x0); } @@ -25,136 +22,105 @@ void Triangle(float x0, float y0, float x1, float y1, float x2, float y2, COLOR int Y2 = (int) (y2 + 0.5f); // Отсечение невидимой части треугольника if (Y0 < 0) Y0 = 0; - else if (Y0 >= height) Y0 = height; + else if (Y0 >= f->height) Y0 = f->height; if (Y1 < 0) Y1 = 0; - else if (Y1 >= height) Y1 = height; + else if (Y1 >= f->height) Y1 = f->height; if (Y2 < 0) Y2 = 0; - else if (Y2 >= height) Y2 = height; + else if (Y2 >= f->height) Y2 = f->height; // Рисование верхней части треугольника - for (float y = Y0 + 0.5f; y < Y1; y++) - { + for (float y = Y0 + 0.5f; y < Y1; y++) { int X0 = (int) ((y - y0) / (y1 - y0) * (x1 - x0) + x0 + 0.5f); int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); if (X0 > X1) swap(X0, X1); if (X0 < 0) X0 = 0; - if (X1 > width) X1 = width; - for (int x = X0; x < X1; x++) - { + if (X1 > f->width) X1 = f->width; + for (int x = X0; x < X1; x++) { // f(x + 0.5, y) - SetPixel(x, y, shader.getColor(x + 0.5f, y)); + SetPixel(x, y, shader->getColor(x + 0.5f, y)); } } // Рисование нижней части треугольника - for (float y = Y1 + 0.5f; y < Y2; y++) - { - int X0 = (int)((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f); - int X1 = (int)((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); + for (float y = Y1 + 0.5f; y < Y2; y++) { + int X0 = (int) ((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f); + int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); if (X0 > X1) swap(X0, X1); if (X0 < 0) X0 = 0; - if (X1 > width) X1 = width; - for (int x = X0; x < X1; x++) - { + if (X1 > f->width) X1 = f->width; + for (int x = X0; x < X1; x++) { // f(x + 0.5, y) - SetPixel(x, y, shader.getColor(x + 0.5f, y)); + SetPixel(x, y, shader->getColor(x + 0.5f, y)); } } } -class BarycentricInterpolator -{ +class BarycentricInterpolator { float x0, y0, x1, y1, x2, y2, S; COLOR C0, C1, C2; public: BarycentricInterpolator(float _x0, float _y0, float _x1, float _y1, float _x2, float _y2, COLOR A0, COLOR A1, COLOR A2) : x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), - S((_y1 - _y2)*(_x0 - _x2) + (_x2 - _x1)*(_y0 - _y2)), C0(A0), C1(A1), - C2(A2) - { + S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), C0(A0), C1(A1), + C2(A2) { } - COLOR getColor(float x, float y) - { + + COLOR getColor(float x, float y) { // Барицентрическая интерполяция float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S; float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S; float h2 = 1 - h0 - h1; - float r = h0 * C0.RED + h1 * C1.RED + h2 * C2.RED; - float g = h0 * C0.GREEN + h1 * C1.GREEN + h2 * C2.GREEN; - float b = h0 * C0.BLUE + h1 * C1.BLUE + h2 * C2.BLUE; - float a = h0 * C0.ALPHA + h1 * C1.ALPHA + h2 * C2.ALPHA; + float r = h0 * C0.red + h1 * C1.red + h2 * C2.red; + float g = h0 * C0.green + h1 * C1.green + h2 * C2.green; + float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue; + float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha; return COLOR(r, g, b, a); } }; -class RadialBrush -{ +class RadialBrush { float cx, cy; // Центр прямоугольника COLOR C0, C1; // Цвета радиальной заливки float angle; // Начальный угол заливки public: - RadialBrush (float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float + RadialBrush(float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float _angle) : - cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1)/ 2.0f), - C0(A0), C1(A1), angle(_angle) - { + cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1) / 2.0f), + C0(A0), C1(A1), angle(_angle) { } - COLOR getColor(float x, float y) - { - double dx = (double)x - cx, dy = (double)y - cy; - double radius = sqrt(dx*dx + dy*dy); + + COLOR getColor(float x, float y) { + double dx = (double) x - cx, dy = (double) y - cy; + double radius = sqrt(dx * dx + dy * dy); float h0 = (sin(radius / 10 + angle) + 1.0f) / 2; float h1 = 1 - h0; - float r = h0 * C0.RED + h1 * C1.RED; - float g = h0 * C0.GREEN + h1 * C1.GREEN; - float b = h0 * C0.BLUE + h1 * C1.BLUE; + float r = h0 * C0.red + h1 * C1.red; + float g = h0 * C0.green + h1 * C1.green; + float b = h0 * C0.blue + h1 * C1.blue; return COLOR(r, g, b); } }; -void ColorFromHSV(double hue, double saturation, double value, GLint *color) -{ +COLOR ColorFromHSV(double hue, double saturation, double value) { int hi = int(floor(hue / 60)) % 6; double f = hue / 60 - floor(hue / 60); value = value * 255; - int v = (int)(value); - int p = (int)(value * (1 - saturation)); - int q = (int)(value * (1 - f * saturation)); - int t = (int)(value * (1 - (1 - f) * saturation)); - if (hi == 0) - { - color[0] = v; - color[1] = t; - color[2] = p; - } - else if (hi == 1) - { - color[0] = q; - color[1] = v; - color[2] = p; - } - else if (hi == 2) - { - color[0] = p; - color[1] = v; - color[2] = t; - } - else if (hi == 3) - { - color[0] = p; - color[1] = q; - color[2] = v; - } - else if (hi == 4) - { - color[0] = t; - color[1] = p; - color[2] = v; - } - else - { - color[0] = v; - color[1] = p; - color[2] = q; + const auto v = (unsigned char) (value); + const auto p = (unsigned char) (value * (1 - saturation)); + const auto q = (unsigned char) (value * (1 - f * saturation)); + const auto t = (unsigned char) (value * (1 - (1 - f) * saturation)); + switch (hi) { + case 0: + return COLOR{v, t, p}; + case 1: + return COLOR{q, v, p}; + case 2: + return COLOR{p, v, t}; + case 3: + return COLOR{p, q, v}; + case 4: + return COLOR{t, p, v}; + default: + return COLOR{v, p, q}; } }