#include "TextureManager.h" #ifdef PNG_ENABLED #include "png.h" #endif #include namespace ZL { Texture::Texture(const TextureDataStruct& texData) { width = texData.width; height = texData.height; glGenTextures(1, &texID); if (texID == 0) { throw std::runtime_error("glGenTextures did not work"); } glBindTexture(GL_TEXTURE_2D, texID); CheckGlError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); CheckGlError(); //This should be only for Windows //glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); CheckGlError(); if (texData.bitSize == TextureDataStruct::BS_24BIT) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, static_cast(texData.width), static_cast(texData.height), 0, GL_RGB, GL_UNSIGNED_BYTE, &texData.data[0]); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast(texData.width), static_cast(texData.height), 0, GL_RGBA, GL_UNSIGNED_BYTE, &texData.data[0]); } CheckGlError(); } Texture::Texture(const std::array& texDataArray) { // Проверка, что все грани имеют одинаковые размеры width = texDataArray[0].width; height = texDataArray[0].height; for (size_t i = 1; i < 6; ++i) { if (texDataArray[i].width != width || texDataArray[i].height != height) { throw std::runtime_error("Cubemap faces must have the same dimensions"); } } glGenTextures(1, &texID); if (texID == 0) { throw std::runtime_error("glGenTextures did not work for cubemap"); } glBindTexture(GL_TEXTURE_CUBE_MAP, texID); CheckGlError(); // Настройка параметров для Cubemap glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Используем GL_LINEAR для MIN_FILTER, так как мипмапы здесь не генерируются // Если бы использовались мипмапы (e.g., GL_LINEAR_MIPMAP_LINEAR), нужно было бы вызвать glGenerateMipmap. glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Обязательные параметры обертки для Cubemap glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // GL_TEXTURE_WRAP_R не поддерживается в WebGL 1.0/OpenGL ES 2.0 и вызывает ошибку. // Ограничиваем его вызов только для настольных платформ. #ifndef EMSCRIPTEN glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); #endif CheckGlError(); // Проверка после установки параметров // Загрузка данных для каждой из 6 граней // GL_TEXTURE_CUBE_MAP_POSITIVE_X + i дает грани: +X (0), -X (1), +Y (2), -Y (3), +Z (4), -Z (5) for (int i = 0; i < 6; ++i) { GLint internalFormat; GLenum format; // В WebGL 1.0/OpenGL ES 2.0 внутренний формат (internalFormat) // должен строго соответствовать формату данных (format). if (texDataArray[i].bitSize == TextureDataStruct::BS_24BIT) { internalFormat = GL_RGB; // internalFormat format = GL_RGB; // format } else // BS_32BIT { internalFormat = GL_RGBA; // internalFormat format = GL_RGBA; // format } glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // Целевая грань 0, // Уровень MIP-текстуры internalFormat, // Внутренний формат (должен совпадать с форматом) static_cast(width), static_cast(height), 0, // Граница (всегда 0) format, // Формат исходных данных GL_UNSIGNED_BYTE, // Тип данных texDataArray[i].data.data() // Указатель на данные ); CheckGlError(); } // Снимаем привязку для чистоты glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } Texture::~Texture() { glDeleteTextures(1, &texID); texID = 0; } GLuint Texture::getTexID() { return texID; } size_t Texture::getWidth() { return width; } size_t Texture::getHeight() { return height; } TextureDataStruct CreateTextureDataFromBmp24(const std::string& fullFileName, const std::string& ZIPFileName) { TextureDataStruct texData; std::vector fileArr; fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName); size_t fileSize = fileArr.size(); if (fileSize < 22) { throw std::runtime_error("File is too short or not correct!"); } //This refers to BITMAPV5HEADER texData.width = *reinterpret_cast(&fileArr[18]); texData.height = *reinterpret_cast(&fileArr[22]); texData.bitSize = TextureDataStruct::BS_24BIT; size_t dataSize = texData.width * texData.height * 3; texData.data.resize(dataSize); size_t pos = *reinterpret_cast(&fileArr[10]); size_t x = 0; for (size_t i = 0; i < texData.width; i++) for (size_t j = 0; j < texData.height; j++) { if (pos + 3 > fileSize) { throw std::runtime_error("File is too short!"); } x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j); texData.data[x + 2] = fileArr[pos++]; texData.data[x + 1] = fileArr[pos++]; texData.data[x + 0] = fileArr[pos++]; } return texData; } TextureDataStruct CreateTextureDataFromBmp32(const std::string& fullFileName, const std::string& ZIPFileName) { TextureDataStruct texData; std::vector fileArr; fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName); size_t fileSize = fileArr.size(); if (fileSize < 22) { throw std::runtime_error("File is too short or not correct!"); } //This refers to BITMAPV5HEADER texData.width = *reinterpret_cast(&fileArr[18]); texData.height = *reinterpret_cast(&fileArr[22]); texData.bitSize = TextureDataStruct::BS_32BIT; size_t dataSize = texData.width * texData.height * 4; texData.data.resize(dataSize); size_t pos = *reinterpret_cast(&fileArr[10]); size_t x = 0; for (size_t i = 0; i < texData.width; i++) for (size_t j = 0; j < texData.height; j++) { if (pos + 4 > fileSize) { throw std::runtime_error("File is too short!"); } x = (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j) + (i * texData.height + j); texData.data[x + 2] = fileArr[pos++]; texData.data[x + 1] = fileArr[pos++]; texData.data[x + 0] = fileArr[pos++]; texData.data[x + 3] = fileArr[pos++]; } return texData; } #ifdef PNG_ENABLED // Структура для хранения данных о файле/массиве и текущей позиции чтения struct png_data_t { const char* data; size_t size; size_t offset; }; // Пользовательская функция чтения для libpng // 'png_ptr' - указатель на структуру png // 'out_ptr' - куда записывать прочитанные данные // 'bytes_to_read' - сколько байт нужно прочитать void user_read_data(png_structp png_ptr, png_bytep out_ptr, png_size_t bytes_to_read) { // Получаем указатель на нашу структуру png_data_t, которую мы установили с помощью png_set_read_fn png_data_t* data = (png_data_t*)png_get_io_ptr(png_ptr); if (data->offset + bytes_to_read > data->size) { // Попытка прочитать больше, чем есть в массиве. // Вместо вызова стандартной ошибки, мы можем просто прочитать остаток или вызвать ошибку. // В этом случае мы вызовем ошибку libpng. png_error(png_ptr, "PNG Read Error: Attempted to read past end of data buffer."); bytes_to_read = data->size - data->offset; // Устанавливаем, чтобы прочитать оставшееся } // Копируем данные из нашего массива в буфер libpng std::memcpy(out_ptr, data->data + data->offset, bytes_to_read); // Обновляем смещение data->offset += bytes_to_read; } // Пользовательская функция предупреждений (по желанию, можно использовать nullptr) void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { // Здесь можно реализовать логирование предупреждений //throw std::runtime_error(); std::cout << "PNG Warning: " << warning_msg << std::endl; } // Пользовательская функция ошибок (обязательна для setjmp) void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { // Здесь можно реализовать логирование ошибок std::cout << "PNG Error: " << error_msg << std::endl; // Обязательно вызываем longjmp для выхода из процесса чтения/записи PNG longjmp(png_jmpbuf(png_ptr), 1); } TextureDataStruct CreateTextureDataFromPng(const std::vector& fileArr) { TextureDataStruct texData; // Структура для управления чтением из массива png_data_t png_data = { fileArr.data(), fileArr.size(), 0 }; png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { throw std::runtime_error("Could not create PNG read structure"); } png_infop info = png_create_info_struct(png); if (!info) { png_destroy_read_struct(&png, nullptr, nullptr); throw std::runtime_error("Could not create PNG info structure"); } // === Установка пользовательских функций чтения и обработки ошибок === // 1. Установка обработчика ошибок и longjmp if (setjmp(png_jmpbuf(png))) { png_destroy_read_struct(&png, &info, nullptr); throw std::runtime_error("Error during PNG read (longjmp was executed)"); } // 2. Установка пользовательских функций для обработки ошибок и предупреждений // Вместо nullptr в error_ptr и warning_ptr можно передать указатель на свою структуру данных, если необходимо png_set_error_fn(png, nullptr, user_error_fn, user_warning_fn); // 3. Установка пользовательской функции чтения и передача ей нашей структуры png_data png_set_read_fn(png, &png_data, user_read_data); // =================================================================== png_read_info(png, info); texData.width = png_get_image_width(png, info); texData.height = png_get_image_height(png, info); png_byte color_type = png_get_color_type(png, info); png_byte bit_depth = png_get_bit_depth(png, info); // === Блок преобразований (оставлен без изменений) === if (bit_depth == 16) png_set_strip_16(png); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); png_read_update_info(png, info); // ==================================================== // === Чтение пикселей (оставлен без изменений) === png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * texData.height); for (int y = 0; y < texData.height; y++) { row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); } png_read_image(png, row_pointers); bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) || (png_get_valid(png, info, PNG_INFO_tRNS)); size_t dataSize; if (has_alpha) { texData.bitSize = TextureDataStruct::BS_32BIT; } else { texData.bitSize = TextureDataStruct::BS_24BIT; } int channels = has_alpha ? 4 : 3; dataSize = texData.width * texData.height * channels; texData.data.resize(dataSize); for (int y = texData.height - 1; y >= 0; y--) { png_bytep row = row_pointers[texData.height - 1 - y]; for (int x = 0; x < texData.width; x++) { png_bytep px = &(row[x * 4]); texData.data[(y * texData.width + x) * channels + 0] = px[0]; // R texData.data[(y * texData.width + x) * channels + 1] = px[1]; // G texData.data[(y * texData.width + x) * channels + 2] = px[2]; // B if (has_alpha) { texData.data[(y * texData.width + x) * channels + 3] = px[3]; // A } } free(row_pointers[texData.height - 1 - y]); } free(row_pointers); // ================================================== png_destroy_read_struct(&png, &info, nullptr); return texData; } TextureDataStruct CreateTextureDataFromPng(const std::string& fullFileName, const std::string& ZIPFileName) { std::vector fileArr; fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName); if (fileArr.empty()) { throw std::runtime_error("Could not read file data into memory"); } // Вызываем новую функцию, которая работает с массивом байт return CreateTextureDataFromPng(fileArr); } #endif }