diff --git a/src/frame.hpp b/src/frame.hpp index 1a0390b..a052481 100644 --- a/src/frame.hpp +++ b/src/frame.hpp @@ -42,16 +42,16 @@ public: } - void SetPixel(int x, int y, COLOR color) { + void set_pixel(int x, int y, COLOR color) { matrix[y][x] = color; } - COLOR GetPixel(int x, int y) { + COLOR get_pixel(int x, int y) { return matrix[y][x]; } - void Circle(int x0, int y0, int radius, COLOR color) { + void draw_circle(int x0, int y0, int radius, COLOR outline) { int x = 0, y = radius; while (x < y) { // Определяем, какая точка (пиксель): (x, y) или (x, y - 1) ближе к линии окружности @@ -63,20 +63,20 @@ public: 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); + set_pixel(x0 + x, y0 + y, outline); + set_pixel(x0 + x, y0 - y, outline); + set_pixel(x0 + y, y0 + x, outline); + set_pixel(x0 + y, y0 - x, outline); + set_pixel(x0 - x, y0 + y, outline); + set_pixel(x0 - x, y0 - y, outline); + set_pixel(x0 - y, y0 + x, outline); + set_pixel(x0 - y, y0 - x, outline); x++; } } - void DrawLine(int x1, int y1, int x2, int y2, COLOR color) { + void draw_line(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; diff --git a/src/main.cpp b/src/main.cpp index dfdce00..2f3d890 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,8 +27,9 @@ int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, wcl.hCursor = LoadCursor(nullptr, IDC_ARROW); // Курсор wcl.lpszMenuName = nullptr; // Без меню wcl.cbClsExtra = 0; // Без дополнительной информации - wcl.cbWndExtra = sizeof(Painter *); - static_assert(sizeof(Painter *) == sizeof(LONG_PTR)); + wcl.cbWndExtra = sizeof(PainterState *) + sizeof(Painter **); + static_assert(sizeof(PainterState *) == sizeof(LONG_PTR)); + static_assert(sizeof(Painter **) == sizeof(LONG_PTR)); wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); //Белый фон @@ -74,22 +75,18 @@ int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, } -static Painter painter; - // Следующая функция вызывается операционной системой Windows и получает в качестве // параметров сообщения из очереди сообщений данного приложения LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static int pixelSize = 8; // Размер "большого" пикселя switch (message) { - case WM_CREATE: { + case WM_CREATE: SetTimer(hWnd, 1, 1000 / 30, nullptr); - SetWindowLongPtr(hWnd, 0, reinterpret_cast(new Painter{})); - } + SetWindowLongPtr(hWnd, 0, (LONG_PTR) new PainterState()); + SetWindowLongPtr(hWnd, sizeof(PainterState *), (LONG_PTR) &predefined_painters[0]); break; - - // Обработка сообщения на перерисовку окна case WM_PAINT: { PAINTSTRUCT ps; @@ -110,8 +107,10 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara int H = (height - 22) / ratio; // Отнимем высоту StatusBar'а Frame frame(W, H); - auto *painter = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); - painter->Draw(frame); + auto *state = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + auto *painter = *reinterpret_cast(GetWindowLongPtr(hWnd, sizeof(PainterState *))); + PixelGridPainter{COLOR{245, 245, 245}, COLOR{240, 240, 240}}.draw(state, &frame); + painter->draw(state, &frame); // Системная структура для хранения цвета пикселя // Буфер кадра, который будет передаваться операционной системе, должен состоять из массива этих структур @@ -132,7 +131,7 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara 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); + COLOR color = frame.get_pixel(x / ratio, y / ratio); pixel->RED = color.red; pixel->GREEN = color.green; pixel->BLUE = color.blue; @@ -190,8 +189,8 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara break; case WM_LBUTTONDOWN: - reinterpret_cast(GetWindowLongPtr(hWnd, 0))->SetClickedPixel(LOWORD(lParam) / pixelSize, HIWORD(lParam) / pixelSize); - InvalidateRect(hWnd, NULL, false); + reinterpret_cast(GetWindowLongPtr(hWnd, 0))->set_clicked_pixel(LOWORD(lParam) / pixelSize, HIWORD(lParam) / pixelSize); + InvalidateRect(hWnd, nullptr, false); break; case WM_KEYDOWN: @@ -199,7 +198,7 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara if (pixelSize > 1 && wParam == VK_F2) pixelSize--; if (pixelSize < 64 && wParam == VK_F3) pixelSize++; - InvalidateRect(hWnd, NULL, false); + InvalidateRect(hWnd, nullptr, false); char str[256]; sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); @@ -208,6 +207,14 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara if (wParam == VK_F1) { MessageBoxA(hWnd, "Работу выполнил студент группы ПВ-221 Колесников А.И.", "О программе", MB_ICONINFORMATION); } + if (wParam == VK_F4) { + auto newPtr = reinterpret_cast(GetWindowLongPtr(hWnd, sizeof(PainterState *))); + newPtr++; + if (newPtr == nullptr) + newPtr = &predefined_painters[0]; + + SetWindowLongPtr(hWnd, sizeof(PainterState *), (LONG_PTR) newPtr); + } break; // Обработка сообщения на изменение размера окна @@ -217,13 +224,13 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara break; case WM_TIMER: - reinterpret_cast(GetWindowLongPtr(hWnd, 0))->AngleStep(); + reinterpret_cast(GetWindowLongPtr(hWnd, 0))->angle += 0.05; InvalidateRect(hWnd, nullptr, false); break; case WM_DESTROY: - delete reinterpret_cast(GetWindowLongPtr(hWnd, 0)); + delete reinterpret_cast(GetWindowLongPtr(hWnd, 0)); SetWindowLongPtr(hWnd, 0, reinterpret_cast(nullptr)); PostQuitMessage(0); break; diff --git a/src/painter.cpp b/src/painter.cpp index 087379a..d63e850 100644 --- a/src/painter.cpp +++ b/src/painter.cpp @@ -1,59 +1,61 @@ +#include #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 }); // Белый цвет +class DemoPainter : public Painter { + + void draw(PainterState *state, Frame *frame) const override { + + 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 = state->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->draw_line( // Добавляем везде 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)); + } - 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; + // Рисуем описанную окружность + frame->draw_circle((int) C.x, (int) C.y, int(a * sqrt(2) + 0.5f), COLOR(100, 100, 250)); - // Инициализируем исходные координаты центра и вершин квадрата - 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; + // Рисуем пиксель, на который кликнул пользователь + if (state->clicked_pixel.x >= 0 && state->clicked_pixel.x < W && + state->clicked_pixel.y >= 0 && state->clicked_pixel.y < H) + frame->set_pixel(state->clicked_pixel.x, state->clicked_pixel.y, {34, 175, 60}); // Пиксель зелёного цвета } +}; - // Рисуем стороны квадрата - 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)); - } +static auto predefined_painters_ = std::make_tuple( + DemoPainter() +); - // Рисуем описанную окружность - 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}); // Пиксель зелёного цвета -} +Painter const *const predefined_painters[] = { + &std::get<0>(predefined_painters_), + nullptr +}; \ No newline at end of file diff --git a/src/painter.hpp b/src/painter.hpp index 9e52535..96bb9a7 100644 --- a/src/painter.hpp +++ b/src/painter.hpp @@ -2,26 +2,44 @@ #include "frame.hpp" +class PainterState { +public: + float angle = 0; + struct { + unsigned short x = -1, y = -1; + } clicked_pixel; + + PainterState() = default; + + void set_clicked_pixel(unsigned short x, unsigned short y) { + this->clicked_pixel.x = x; + this->clicked_pixel.y = y; + } +}; 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); + virtual void draw(PainterState *state, Frame *frame) const = 0; }; + +extern Painter const *const predefined_painters[]; + + +class PixelGridPainter : public Painter { +private: + const COLOR c1; + const COLOR c2; +public: + inline PixelGridPainter(COLOR c1, COLOR c2) : c1{c1}, c2{c2} {} + + inline void draw(PainterState *state, Frame *frame) const override { + for (int y = 0; y < frame->height; y++) { + for (int x = 0; x < frame->width; x++) { + if ((x + y) % 2 == 0) + frame->set_pixel(x, y, this->c1); + else + frame->set_pixel(x, y, this->c2); + } + } + } +}; \ No newline at end of file