Преобразование изображения в значения пикселей (PNG + STB_IMAGE)
1) Немного теории про .PNG
Почитать про .PNG можно здесь;
Вкратце, .png состоит из двух основных частей:
Подпись
.png;Чанки (
chunks) с данными.
Обобщенный формат PNG-файла
Подпись PNG
Подпись PNG-файла состоит из 8 байт и представляет собой (в hex-записи):
0x89 0x50 0x4e 0x47 0xd 0xa 0x1a 0xa
, где:
Значение (hex) |
Назначение |
|---|---|
0x89 |
Non-ASCII символ. Препятствует распознаванию PNG, как текстового файла. |
0x50 0x4e 0x47 |
PNG в ASCII записи. |
0D 0A |
CRLF (Carriage-return, Line-feed), DOS-style перевод строки. |
0x1a |
Останавливает вывод файла в DOS режиме (end-of-file), чтобы вам не вываливалось многокилобайтное изображение в текстовом виде. |
0xa |
LF, Unix-style перевод строки. |
Чанки (chunks) PNG
Чанки — это блоки данных, из которых состоит файл. Каждый чанк состоит из 4 секций.
Length (длина) |
Type (тип) |
Data (данные) |
CRC |
|---|---|---|---|
4 байта |
4 байта |
Length байт |
4 байта |
, где:
Длина — это числовое значение длины блока данных;
Тип - представляет собой 4 чувствительных к регистру
ASCII-символа;Данные - собственно, само изображение;
CRC - контрольная сумма для проверки целостности.
Также, важно отметить, что существуют Критические чанки:
IHDR— заголовок файла, содержит основную информацию о изображении. Обязан быть первым чанком.PLTE— палитра, список цветов.IDAT— содержит, собственно, изображение. Рисунок можно разбить на несколькоIDATчанков, для потоковой передачи. В каждом файле должен быть хотя бы один -IDATчанк.IEND— завершающий чанк, обязан быть последним в файле.
Минимальный PNG-файл выглядит следующим образом:
Минимальный PNG-файл.
2) Библиотека STB
Баблиотека (на базе С) STB является довольно популярной (33.2k stars on github) библиотекой для чтения изображений формата STB, библиотека состоит из одного заголовочного файла.
Есть более полный список библиотек для работы с изображениями и не только: https://habr.com/ru/articles/831754/.
Устанавливаем пакеты:
sudo apt install libstb-dev
Добавляем в CmakLists нашего проекта (где уже подключены ImGUI + PSQL + curl):
include(FindPkgConfig)
pkg_check_modules(STB REQUIRED stb)
add_executable(tile ${EXAMPLES_DIR}/osm_tiles/tile_catcher.cpp)
target_link_libraries(tile PRIVATE imgui implot curl ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES} ${GLEW_LIBRARIES} ${STB_LIBRARIES})
target_include_directories(tile PRIVATE ${STB_INCLUDE_DIRS})
Имя |
Что делает? |
|---|---|
|
загрузка изображений из оперативной памяти, а не с диска.
Она принимает указатель на буфер данных, размер, возвращает пиксели и параметры изображения (x, y, channels).
Поддерживает
JPEG, PNG, TGA, BMP, GIF, HDR и др., возвращая stbi_uc (unsigned char). |
|
функция для освобождения памяти, выделенной функциями |
|
позволяет получить информацию об изображении (ширину, высоту, количество компонентов) без полной загрузки пикселей. |
stbi_load_from_memory()
std::vector<unsigned char> _rawBlob; // Байты PNG-изображения
data = stbi_load_from_memory(
_rawBlob.data(), // указатель на байты PNG-изображения
_rawBlob.size(), // Количество байт
&_width, // Ширины изображения
&_height, // Высота изображения
&_channels, // Количество RGB-каналов
STBI_rgb_alpha
);
Преобразование **.PNG ** в пиксельную карту (pixel map)
Преобразование будет выполнять при помощи библиотеки libstb-dev, которую мы подключили в CMakeLists в начале.
...
#include <stb_image.h>
...
int _width{256}, _height{256}, _channels{}; // Размеры изображения
std::vector<unsigned char> _rawBlob; // Байтики PNG-изображения
unsigned char *data // Байтики пикселей
GLuint _id{0};
// Преобразуем PNG в rgba-массив
void stbLoad() {
data = stbi_load_from_memory(
_rawBlob.data(), // указатель на байты PNG-изображения
_rawBlob.size(), // Количество байт
&_width, // Ширины изображения
&_height, // Высота изображения
&_channels, // Количество RGB-каналов
STBI_rgb_alpha
);
}
// Преобразуем в текстуру GL
void glLoad(){
glGenTextures(1, &_id);
glBindTexture(GL_TEXTURE_2D, _id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, data);
}
int main(){
...
while(1){
...
ImPlot::BeginPlot("##ImOsmMapPlot");
// Параметры для отображения картинки
ImVec2 _uv0{0, 1}; // Top-left of the texture
ImVec2 _uv1{1, 0}; // Bottom-right of the texture
ImVec4 _tint{1, 1, 1, 1}; // Цвет, накладываемый поверх нашего изображения
ImVec2 bmin{0, 0};
ImVec2 bmax{256, 256};
_rawBlob = ЗДЕСЬ ВЫ выполняете CURL-запрос (x, y, z)-тайла
stbLoad();
glLoad();
// Отображаем текстуру GL - _id, которую мы создали из RGBa
ImPlot::PlotImage("##", _id, bmin, bmax, _uv0, _uv1, _tint);
ImPlot::EndPlot();
...
}
...
}
Если вывести на экран первые несколько значений std::vector<unsigned char> data;, увидим следующее:
228, 228, 227, 255
156, 156, 155, 255
148, 147, 147, 255
212, 212, 211, 255
245, 244, 243, 255
242, 239, 233, 255
230, 228, 220, 255
160, 159, 155, 255
212, 209, 206, 255
242, 239, 233, 255
242, 239, 233, 255
242, 239, 233, 255
242, 239, 233, 255
242, 239, 233, 255
237, 235, 229, 255
197, 218, 184, 255
149, 177, 92, 255
180, 188, 100, 255
247, 250, 191, 255
247, 250, 191, 255
247, 250, 191, 255
247, 250, 191, 255
247, 250, 191, 255
247, 250, 191, 255
247, 250, 191, 255
Готово:
Полный пример можно найти здесь.
Результат.