как повернуть изображение на 180 градусов в цикле : int main()
{
	Vec3f light_dir(0, 0, -1);
	
	const char* buffer;
	Model* model = new Model(buffer);
	for (int i = 0; i < model->nfaces(); i++) {
		vector<int> face = model->face(i);
		Vec2i screen_coords[3];
		Vec3f world_coords[3];
		for (int j = 0; j < 3; j++) {
			Vec3f v = model->vert(face[j]);
			screen_coords[j] = Vec2i((v.x + 1.) * w / 2., (v.y + 1.) * h / 2.);
			world_coords[j] = v;
		}
		Vec3f n = (world_coords[2] - world_coords[0]) ^ (world_coords[1] - world_coords[0]);
		n.normalize();
		float intensity = n * light_dir;
		if (intensity > 0) {
			triangle(screen_coords[0], screen_coords[1], screen_coords[2], image, { rand() % 255, rand() % 255, rand() % 255 });
		}
	}
	png::PngWriter writer;
	writer.initialize(w, h);
	for (uint32_t y = 0; y < h; ++y) {
		for (uint32_t x = 0; x < w; ++x) {
			uint32_t color = (image[x][y].r << 24) | \
							 (image[x][y].g << 16) | \
							 (image[x][y].b << 8)| 255;
			writer.setPixel(x, y, color);
		}
	}
	writer.save("output.png");
	/*ofstream file("image.ppm");
	// формат PPM имеет 2 режима P3 (ASCII) и P6 (бинарный)
	// дальше идёт разрешение изображения (600х600) и
	// максимальное значение цвета (255) 8 бит
	file << "P3\n"
		<< w << " " << h << "\n255\n";
	for (int y = h; y > 0; y--)
	{
		for (int x = 0; x < w; x++)
		{
			file << image[x][y].r << " " << image[x][y].g << " " << image[x][y].b << " ";
		}
		file << "\n";
	}
	file.close();*/

	return 0;
 }
 png.h:
 #pragma once

#include <cstdint>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <cstring>

namespace png {

	// Структура для хранения изображения
	struct Image {
		uint32_t width;
		uint32_t height;
		std::vector<uint8_t> data; // Формат: RGBA
	};

	// Класс для создания PNG-файлов
	class PngWriter {
	public:
		// Устанавливает пиксель по координатам (x, y) с заданным цветом (RGBA)
		void setPixel(uint32_t x, uint32_t y, uint32_t color) {
			if (x >= width_ || y >= height_) {
				std::cerr << "Координаты пикселя (" << x << ", " << y << ") выходят за границы изображения.\n";
				return;
			}
			size_t index = y * (width_ * 4 + 1) + 1 + x * 4;
			imageData_[index + 0] = (color >> 24) & 0xFF; // R
			imageData_[index + 1] = (color >> 16) & 0xFF; // G
			imageData_[index + 2] = (color >> 8) & 0xFF;  // B
			imageData_[index + 3] = color & 0xFF;         // A
		}

		// Сохраняет изображение в файл
		bool save(const std::string& filename) const {
			if (width_ == 0 || height_ == 0) {
				std::cerr << "Размеры изображения не инициализированы.\n";
				return false;
			}

			std::ofstream file(filename, std::ios::binary);
			if (!file) {
				std::cerr << "Не удалось открыть файл для записи: " << filename << "\n";
				return false;
			}

			// Запись сигнатуры PNG
			uint8_t png_signature[8] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' };
			file.write(reinterpret_cast<char*>(png_signature), 8);

			// IHDR данные
			uint8_t ihdr_data[13];
			ihdr_data[0] = (width_ >> 24) & 0xFF;
			ihdr_data[1] = (width_ >> 16) & 0xFF;
			ihdr_data[2] = (width_ >> 8) & 0xFF;
			ihdr_data[3] = width_ & 0xFF;
			ihdr_data[4] = (height_ >> 24) & 0xFF;
			ihdr_data[5] = (height_ >> 16) & 0xFF;
			ihdr_data[6] = (height_ >> 8) & 0xFF;
			ihdr_data[7] = height_ & 0xFF;
			ihdr_data[8] = 8;  // Bit depth
			ihdr_data[9] = 6;  // Color type (RGBA)
			ihdr_data[10] = 0; // Compression method
			ihdr_data[11] = 0; // Filter method
			ihdr_data[12] = 0; // Interlace method

			// Запись IHDR
			uint32_t ihdr_length = swapEndian(13);
			file.write(reinterpret_cast<char*>(&ihdr_length), 4);
			uint8_t ihdr_type[4] = { 'I', 'H', 'D', 'R' };
			file.write(reinterpret_cast<char*>(ihdr_type), 4);
			file.write(reinterpret_cast<char*>(ihdr_data), 13);

			// Вычисление и запись CRC для IHDR
			uint8_t ihdr_crc_data[17];
			memcpy(ihdr_crc_data, ihdr_type, 4);
			memcpy(ihdr_crc_data + 4, ihdr_data, 13);
			uint32_t ihdr_crc = crcFunc(ihdr_crc_data, 17);
			uint32_t ihdr_crc_be = swapEndian(ihdr_crc);
			file.write(reinterpret_cast<char*>(&ihdr_crc_be), 4);

			// Сжатие данных изображения (простой deflate без оптимизации)
			// В реальных приложениях рекомендуется использовать библиотеку zlib для сжатия
			// Здесь мы реализуем простой безоптимизированный блок для примера
			std::vector<uint8_t> compressed_data;
			compressed_data.push_back(0x78); // Zlib CMF: Compression Method and Flags
			compressed_data.push_back(0x01); // Zlib FLG: No compression, low window size

			const size_t max_block_size = 65535;
			size_t offset = 0;
			size_t remaining = imageData_.size();

			while (remaining > 0) {
				uint8_t block_header;
				if (remaining <= max_block_size) {
					block_header = 0x01; // Последний блок
				}
				else {
					block_header = 0x00; // Еще есть блоки
				}
				compressed_data.push_back(block_header);
				uint16_t len = static_cast<uint16_t>(remaining > max_block_size ? max_block_size : remaining);
				uint16_t nlen = ~len;
				compressed_data.push_back(len & 0xFF);
				compressed_data.push_back((len >> 8) & 0xFF);
				compressed_data.push_back(nlen & 0xFF);
				compressed_data.push_back((nlen >> 8) & 0xFF);
				compressed_data.insert(compressed_data.end(),
					imageData_.begin() + offset,
					imageData_.begin() + offset + len);
				offset += len;
				remaining -= len;
			}

			// Добавление Adler-32
			uint32_t adler = computeAdler32(imageData_.data(), imageData_.size());
			compressed_data.push_back((adler >> 24) & 0xFF);
			compressed_data.push_back((adler >> 16) & 0xFF);
			compressed_data.push_back((adler >> 8) & 0xFF);
			compressed_data.push_back(adler & 0xFF);

			// Запись IDAT
			uint32_t idat_length = swapEndian(static_cast<uint32_t>(compressed_data.size()));
			file.write(reinterpret_cast<char*>(&idat_length), 4);
			uint8_t idat_type[4] = { 'I', 'D', 'A', 'T' };
			file.write(reinterpret_cast<char*>(idat_type), 4);
			file.write(reinterpret_cast<char*>(compressed_data.data()), compressed_data.size());

			// Вычисление и запись CRC для IDAT
			std::vector<uint8_t> idat_crc_data(4 + compressed_data.size());
			memcpy(idat_crc_data.data(), idat_type, 4);
			memcpy(idat_crc_data.data() + 4, compressed_data.data(), compressed_data.size());
			uint32_t idat_crc = crcFunc(idat_crc_data.data(), idat_crc_data.size());
			uint32_t idat_crc_be = swapEndian(idat_crc);
			file.write(reinterpret_cast<char*>(&idat_crc_be), 4);

			// Запись IEND
			uint32_t iend_length = 0;
			uint32_t iend_length_be = swapEndian(iend_length);
			file.write(reinterpret_cast<char*>(&iend_length_be), 4);
			uint8_t iend_type[4] = { 'I', 'E', 'N', 'D' };
			file.write(reinterpret_cast<char*>(iend_type), 4);
			uint32_t iend_crc = crcFunc(iend_type, 4);
			uint32_t iend_crc_be = swapEndian(iend_crc);
			file.write(reinterpret_cast<char*>(&iend_crc_be), 4);

			file.close();
			std::cout << "PNG файл успешно сохранен: " << filename << "\n";
			return true;
		}
		// Инициализирует изображение с заданной шириной и высотой
		void initialize(uint32_t width, uint32_t height) {
			width_ = width;
			height_ = height;
			// Каждая строка начинается с байта фильтра (0 - без фильтра)
			imageData_.resize(height_ * (width_ * 4 + 1), 0);
			for (uint32_t y = 0; y < height_; ++y) {
				imageData_[y * (width_ * 4 + 1)] = 0; // Фильтр типа 0
			}
		}
		// Конструктор
		PngWriter() : width_(0), height_(0) {
			makeCrcTable();
		}
		// Деструктор
		~PngWriter() {}
	private:
		// Создание таблицы CRC
		void makeCrcTable() {
			uint32_t c;
			for (uint32_t n = 0; n < 256; n++) {
				c = n;
				for (uint32_t k = 0; k < 8; k++) {
					if (c & 1)
						c = 0xEDB88320L ^ (c >> 1);
					else
						c = c >> 1;
				}
				crc_table_[n] = c;
			}
		}
		// Обновление CRC
		uint32_t updateCrc(uint32_t crc, const uint8_t* buf, size_t len) const {
			uint32_t c = crc;
			for (size_t n = 0; n < len; n++) {
				c = crc_table_[(c ^ buf[n]) & 0xFF] ^ (c >> 8);
			}
			return c;
		}
		// Вычисление CRC
		uint32_t crcFunc(const uint8_t* buf, size_t len) const {
			return updateCrc(0xFFFFFFFFL, buf, len) ^ 0xFFFFFFFFL;
		}
		// Обмен байтами (big-endian <-> little-endian)
		uint32_t swapEndian(uint32_t val) const {
			return ((val >> 24) & 0xff) |
				((val << 8) & 0xff0000) |
				((val >> 8) & 0xff00) |
				((val << 24) & 0xff000000);
		}
		// Вычисление Adler-32
		uint32_t computeAdler32(const uint8_t* data, size_t len) const {
			uint32_t A = 1;
			uint32_t B = 0;
			for (size_t i = 0; i < len; i++) {
				A = (A + data[i]) % 65521;
				B = (B + A) % 65521;
			}
			return (B << 16) | A;
		}

		uint32_t width_;
		uint32_t height_;
		std::vector<uint8_t> imageData_;
		mutable uint32_t crc_table_[256];
	};

} // namespace png