Refactoring shitcode (stage 1)

This commit is contained in:
Andrew Golovashevich 2024-10-09 14:29:38 +03:00
parent 81295e0514
commit 01ae77c9a9
8 changed files with 352 additions and 434 deletions

View File

@ -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
)

View File

@ -1,164 +0,0 @@
#ifndef FRAME_H
#define FRAME_H
#include <math.h>
// 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<typename TYPE> 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

View File

@ -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

125
src/frame.hpp Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include <cmath>
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<typename TYPE>
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;
}
};

View File

@ -4,8 +4,8 @@
#include <Windows.h>
#include <commctrl.h>
#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,46 +23,48 @@ 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<LONG_PTR>(new Painter{}));
}
break;
break;
// Обработка сообщения на перерисовку окна
case WM_PAINT:
{
// Обработка сообщения на перерисовку окна
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
@ -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<Painter *>(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<Painter *>(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<Painter *>(GetWindowLongPtr(hWnd, 0))->AngleStep();
InvalidateRect(hWnd, nullptr, false);
break;
case WM_DESTROY: // Завершение программы
case WM_DESTROY:
delete reinterpret_cast<Painter *>(GetWindowLongPtr(hWnd, 0));
SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(nullptr));
PostQuitMessage(0);
break;
default:
// Все сообщения, не обрабатываемые в данной функции, направляются на обработку по умолчанию
return DefWindowProcA(hWnd, message, wParam, lParam);
}
}
return 0;
}

59
src/painter.cpp Normal file
View File

@ -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}); // Пиксель зелёного цвета
}

27
src/painter.hpp Normal file
View File

@ -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);
};

View File

@ -1,21 +1,18 @@
#include "Painter.h"
#include "frame.hpp"
#include "painter.hpp"
template <class ShaderClass>
void Triangle(float x0, float y0, float x1, float y1, float x2, float y2, COLOR color, ShaderClass &shader)
{
template<class ShaderClass>
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};
}
}