Importing stub for lab2

This commit is contained in:
Andrew Golovashevich 2024-10-09 13:53:26 +03:00
parent 4da7d43344
commit 69d3a747fe
4 changed files with 513 additions and 0 deletions

15
CMakeLists.txt Normal file
View File

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

164
src/Frame.h Normal file
View File

@ -0,0 +1,164 @@
#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

80
src/Painter.h Normal file
View File

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

254
src/main.cpp Normal file
View File

@ -0,0 +1,254 @@
// lab_1_basics.cpp : Определяет точку входа для приложения.
#include <Windows.h>
#include <commctrl.h>
#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;
}