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