opuntiaOS - an operating system targeting x86 and ARMv7
at master 302 lines 11 kB view raw
1/* 2 * Copyright (C) 2020-2022 The opuntiaOS Project Authors. 3 * + Contributed by Nikita Melekhin <nimelehin@gmail.com> 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include <cstring> 10#include <fcntl.h> 11#include <libfoundation/Logger.h> 12#include <libfoundation/compress/puff.h> 13#include <libg/ImageLoaders/PNGLoader.h> 14#include <memory> 15#include <sys/mman.h> 16#include <sys/stat.h> 17#include <unistd.h> 18 19// #define PNGLOADER_DEGUG 20 21namespace LG { 22namespace PNG { 23 24 constexpr int png_header_size = 8; 25 static uint8_t png_header[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; 26 27 PixelBitmap PNGLoader::load_from_file(const std::string& path) 28 { 29 int fd = open(path.c_str(), O_RDONLY); 30 if (fd < 0) { 31 Logger::debug << "PNGLoader: cant open" << std::endl; 32 return PixelBitmap(); 33 } 34 35 fstat_t stat; 36 fstat(fd, &stat); 37 38 uint8_t* ptr = (uint8_t*)mmap(NULL, stat.size, PROT_READ, MAP_PRIVATE, fd, 0); 39 PixelBitmap bitmap = load_from_mem(ptr); 40 41 munmap(ptr, stat.size); 42 close(fd); 43 return bitmap; 44 } 45 46 bool PNGLoader::check_header(const uint8_t* ptr) const 47 { 48 return memcmp(ptr, (uint8_t*)png_header, png_header_size) == 0; 49 } 50 51 void PNGLoader::read_IHDR(ChunkHeader& header, PixelBitmap& bitmap) 52 { 53 streamer().read(m_ihdr_chunk.width); 54 streamer().read(m_ihdr_chunk.height); 55 streamer().read(m_ihdr_chunk.depth); 56 streamer().read(m_ihdr_chunk.color_type); 57 streamer().read(m_ihdr_chunk.compression_method); 58 streamer().read(m_ihdr_chunk.filter_method); 59 streamer().read(m_ihdr_chunk.interlace_method); 60 61 bitmap.resize(m_ihdr_chunk.width, m_ihdr_chunk.height); 62 63#ifdef PNGLOADER_DEGUG 64 Logger::debug << "IHDR: " << m_ihdr_chunk.width << " " << m_ihdr_chunk.depth << " " << m_ihdr_chunk.compression_method << " " << m_ihdr_chunk.filter_method << " " << m_ihdr_chunk.color_type << std::endl; 65#endif 66 } 67 68 void PNGLoader::read_TEXT(ChunkHeader& header, PixelBitmap& bitmap) 69 { 70 streamer().skip(header.len); 71 } 72 73 void PNGLoader::read_PHYS(ChunkHeader& header, PixelBitmap& bitmap) 74 { 75 streamer().skip(header.len); 76 } 77 78 void PNGLoader::read_ORNT(ChunkHeader& header, PixelBitmap& bitmap) 79 { 80 streamer().skip(header.len); 81 } 82 83 // TODO: Currently support only comprssion type 0 84 void PNGLoader::read_IDAT(ChunkHeader& header, PixelBitmap& bitmap) 85 { 86 size_t buf_size = m_compressed_data.size(); 87 m_compressed_data.resize(buf_size + header.len); 88 memcpy(&m_compressed_data.data()[buf_size], streamer().ptr(), header.len); 89 streamer().skip(header.len); 90 } 91 92 void PNGLoader::process_compressed_data(PixelBitmap& bitmap) 93 { 94 size_t datalen = m_compressed_data.size(); 95 size_t destlen = 0; 96 int ret = puff(0, &destlen, m_compressed_data.data() + 2, &datalen); 97 uint8_t* unzipped_data = (uint8_t*)malloc(destlen); 98 puff(unzipped_data, &destlen, m_compressed_data.data() + 2, &datalen); 99 DataStreamer local_streamer(unzipped_data); 100 m_scanline_keeper.init(unzipped_data); 101 102 if (m_ihdr_chunk.color_type == 2) { 103 m_scanline_keeper.set_color_length(3); 104 if (m_ihdr_chunk.depth == 8) { 105 for (int i = 0; i < m_ihdr_chunk.height; i++) { 106 uint8_t scanline_filter; 107 local_streamer.read(scanline_filter); 108 109 if (scanline_filter > 4) { 110 Logger::debug << "Invalid PNG filter: " << scanline_filter << std::endl; 111 continue; 112 } 113 114 size_t len_of_scanline = (m_scanline_keeper.color_length() * m_ihdr_chunk.width * m_ihdr_chunk.depth + 7) / 8; 115 m_scanline_keeper.add(Scanline(scanline_filter, local_streamer.ptr())); 116 local_streamer.skip(len_of_scanline); 117 } 118 } 119 } else if (m_ihdr_chunk.color_type == 6) { 120 m_scanline_keeper.set_color_length(4); 121 if (m_ihdr_chunk.depth == 8) { 122 for (int i = 0; i < m_ihdr_chunk.height; i++) { 123 uint8_t scanline_filter; 124 local_streamer.read(scanline_filter); 125 126 if (scanline_filter > 4) { 127 Logger::debug << "Invalid PNG filter: " << scanline_filter << std::endl; 128 continue; 129 } 130 131 size_t len_of_scanline = (m_scanline_keeper.color_length() * m_ihdr_chunk.width * m_ihdr_chunk.depth + 7) / 8; 132 m_scanline_keeper.add(Scanline(scanline_filter, local_streamer.ptr())); 133 local_streamer.skip(len_of_scanline); 134 } 135 } 136 } 137 138 unfilter_scanlines(); 139 copy_scanlines_to_bitmap(bitmap); 140 m_scanline_keeper.invalidate(); 141 } 142 143 uint8_t PNGLoader::paeth_predictor(int a, int b, int c) 144 { 145 int p = a + b - c; 146 int pa = abs(p - a); 147 int pb = abs(p - b); 148 int pc = abs(p - c); 149 if (pa <= pb && pa <= pc) 150 return a; 151 if (pb <= pc) 152 return b; 153 return c; 154 } 155 156 void PNGLoader::unfilter_scanlines() 157 { 158 for (int i = 0; i < m_ihdr_chunk.height; i++) { 159 if (m_scanline_keeper.scanlines()[i].filter_type() == 1) { // Sub 160 int width = m_ihdr_chunk.width * m_scanline_keeper.color_length(); 161 for (int j = 0; j < width; j++) { 162 int prev = 0; 163 if (j >= m_scanline_keeper.color_length()) { 164 prev = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()]; 165 } 166 m_scanline_keeper.scanlines()[i].data()[j] += prev; 167 } 168 } else if (m_scanline_keeper.scanlines()[i].filter_type() == 2) { // Up 169 int width = m_ihdr_chunk.width * m_scanline_keeper.color_length(); 170 for (int j = 0; j < width; j++) { 171 int prev = 0; 172 if (i > 0) { 173 prev = m_scanline_keeper.scanlines()[i - 1].data()[j]; 174 } 175 m_scanline_keeper.scanlines()[i].data()[j] += prev; 176 } 177 } else if (m_scanline_keeper.scanlines()[i].filter_type() == 3) { // Average 178 int width = m_ihdr_chunk.width * m_scanline_keeper.color_length(); 179 for (int j = 0; j < width; j++) { 180 int prev = 0; 181 int prior = 0; 182 if (i > 0) { 183 prior = m_scanline_keeper.scanlines()[i - 1].data()[j]; 184 } 185 if (j >= m_scanline_keeper.color_length()) { 186 prev = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()]; 187 } 188 m_scanline_keeper.scanlines()[i].data()[j] += (prev + prior) / 2; 189 } 190 } else if (m_scanline_keeper.scanlines()[i].filter_type() == 4) { // Paeth 191 int width = m_ihdr_chunk.width * m_scanline_keeper.color_length(); 192 for (int j = 0; j < width; j++) { 193 int a = 0; 194 int b = 0; 195 int c = 0; 196 if (i > 0) { 197 b = m_scanline_keeper.scanlines()[i - 1].data()[j]; 198 } 199 if (j >= m_scanline_keeper.color_length()) { 200 a = m_scanline_keeper.scanlines()[i].data()[j - m_scanline_keeper.color_length()]; 201 if (i > 0) { 202 c = m_scanline_keeper.scanlines()[i - 1].data()[j - m_scanline_keeper.color_length()]; 203 } 204 } 205 m_scanline_keeper.scanlines()[i].data()[j] += paeth_predictor(a, b, c); 206 } 207 } 208 } 209 } 210 211 void PNGLoader::copy_scanlines_to_bitmap(PixelBitmap& bitmap) 212 { 213 if (m_ihdr_chunk.color_type == 2) { 214 bitmap.set_format(PixelBitmapFormat::RGB); 215 for (int i = 0; i < m_ihdr_chunk.height; i++) { 216 auto& scanline = m_scanline_keeper.scanlines()[i]; 217 for (int j = 0, bit = 0; j < m_ihdr_chunk.width; j++) { 218 int r = scanline.data()[bit++]; 219 int g = scanline.data()[bit++]; 220 int b = scanline.data()[bit++]; 221 int alpha = 255; 222 bitmap[i][j] = Color(r, g, b, alpha); 223 } 224 } 225 } 226 if (m_ihdr_chunk.color_type == 6) { 227 bitmap.set_format(PixelBitmapFormat::RGBA); 228 for (int i = 0; i < m_ihdr_chunk.height; i++) { 229 auto& scanline = m_scanline_keeper.scanlines()[i]; 230 for (int j = 0, bit = 0; j < m_ihdr_chunk.width; j++) { 231 int r = scanline.data()[bit++]; 232 int g = scanline.data()[bit++]; 233 int b = scanline.data()[bit++]; 234 int alpha = scanline.data()[bit++]; 235 bitmap[i][j] = Color(r, g, b, alpha); 236 } 237 } 238 } 239 } 240 241 bool PNGLoader::read_chunk(PixelBitmap& bitmap) 242 { 243 ChunkHeader header; 244 streamer().read(header.len); 245 streamer().read(header.type, 4); 246 247 if (memcmp(header.type, (uint8_t*)"IHDR", 4) == 0) { 248 read_IHDR(header, bitmap); 249 } else if (memcmp(header.type, (uint8_t*)"tEXt", 4) == 0) { 250 read_TEXT(header, bitmap); 251 } else if (memcmp(header.type, (uint8_t*)"zTXt", 4) == 0) { 252 read_TEXT(header, bitmap); 253 } else if (memcmp(header.type, (uint8_t*)"pHYs", 4) == 0) { 254 read_PHYS(header, bitmap); 255 } else if (memcmp(header.type, (uint8_t*)"sRGB", 4) == 0) { 256 read_PHYS(header, bitmap); 257 } else if (memcmp(header.type, (uint8_t*)"eXIf", 4) == 0) { 258 read_PHYS(header, bitmap); 259 } else if (memcmp(header.type, (uint8_t*)"orNT", 4) == 0) { 260 read_ORNT(header, bitmap); 261 } else if (memcmp(header.type, (uint8_t*)"IDAT", 4) == 0) { 262 read_IDAT(header, bitmap); 263 } else if (memcmp(header.type, (uint8_t*)"IEND", 4) == 0) { 264 return false; 265 } else { 266 Logger::debug << "PNGLoader: Unexpected header type: " << (char*)header.type << std::endl; 267 return false; 268 } 269 270 int crc; 271 streamer().read(crc); 272 273 return true; 274 } 275 276 void PNGLoader::proccess_stream(PixelBitmap& bitmap) 277 { 278 int len = 0; 279 while (read_chunk(bitmap)) { } 280 process_compressed_data(bitmap); 281 } 282 283 PixelBitmap PNGLoader::load_from_mem(const uint8_t* ptr) 284 { 285 auto bitmap = PixelBitmap(); 286 if (!ptr) { 287 Logger::debug << "PNGLoader: nullptr" << std::endl; 288 return bitmap; 289 } 290 291 if (!check_header(ptr)) { 292 Logger::debug << "PNGLoader: not png" << std::endl; 293 return bitmap; 294 } 295 296 streamer().set(ptr + png_header_size); 297 proccess_stream(bitmap); 298 return bitmap; 299 } 300 301} // namespace PNG 302} // namespace LG