1// MIT License
2
3// Copyright (c) 2021 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "png.h"
24#include "defines.h"
25
26#include <string.h>
27#include <stdlib.h>
28#include <png.h>
29#include "tic_assert.h"
30
31#define RGBA_SIZE sizeof(u32)
32
33png_buffer png_create(s32 size)
34{
35 return (png_buffer){malloc(size), size};
36}
37
38typedef struct
39{
40 png_buffer buffer;
41 size_t pos;
42} PngStream;
43
44static void pngReadCallback(png_structp png, png_bytep out, png_size_t size)
45{
46 PngStream* stream = png_get_io_ptr(png);
47 memcpy(out, stream->buffer.data + stream->pos, size);
48 stream->pos += size;
49}
50
51png_img png_read(png_buffer buf)
52{
53 png_img res = { 0 };
54
55 if (png_sig_cmp(buf.data, 0, 8) == 0)
56 {
57 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
58 png_infop info = png_create_info_struct(png);
59
60 PngStream stream = { .buffer = buf};
61
62 png_set_read_fn(png, &stream, pngReadCallback);
63 png_read_info(png, info);
64
65 res.width = png_get_image_width(png, info);
66 res.height = png_get_image_height(png, info);
67 s32 colorType = png_get_color_type(png, info);
68 s32 bitDepth = png_get_bit_depth(png, info);
69
70 if (bitDepth == 16)
71 png_set_strip_16(png);
72
73 if (colorType == PNG_COLOR_TYPE_PALETTE)
74 png_set_palette_to_rgb(png);
75
76 // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
77 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
78 png_set_expand_gray_1_2_4_to_8(png);
79
80 if (png_get_valid(png, info, PNG_INFO_tRNS))
81 png_set_tRNS_to_alpha(png);
82
83 // These colorType don't have an alpha channel then fill it with 0xff.
84 if (colorType == PNG_COLOR_TYPE_RGB ||
85 colorType == PNG_COLOR_TYPE_GRAY ||
86 colorType == PNG_COLOR_TYPE_PALETTE)
87 png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
88
89 if (colorType == PNG_COLOR_TYPE_GRAY ||
90 colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
91 png_set_gray_to_rgb(png);
92
93 png_read_update_info(png, info);
94
95 res.data = malloc(RGBA_SIZE * res.width * res.height);
96 png_bytep* rows = (png_bytep*)malloc(sizeof(png_bytep) * res.height);
97
98 for (s32 i = 0; i < res.height; i++)
99 rows[i] = res.data + res.width * i * RGBA_SIZE;
100
101 png_read_image(png, rows);
102
103 free(rows);
104
105 png_destroy_read_struct(&png, &info, NULL);
106 }
107
108 return res;
109}
110
111static void pngWriteCallback(png_structp png, png_bytep data, png_size_t size)
112{
113 PngStream* stream = png_get_io_ptr(png);
114
115 stream->buffer.size += (u32)size;
116
117 stream->buffer.data = realloc(stream->buffer.data, stream->buffer.size);
118 memcpy(stream->buffer.data + stream->pos, data, size);
119 stream->pos += size;
120}
121
122static void pngFlushCallback(png_structp png) {}
123
124png_buffer png_write(png_img src)
125{
126 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
127 png_infop info = png_create_info_struct(png);
128
129 PngStream stream = {0};
130 png_set_write_fn(png, &stream, pngWriteCallback, pngFlushCallback);
131
132 // Output is 8bit depth, RGBA format.
133 png_set_IHDR(
134 png,
135 info,
136 src.width, src.height,
137 8,
138 PNG_COLOR_TYPE_RGBA,
139 PNG_INTERLACE_NONE,
140 PNG_COMPRESSION_TYPE_DEFAULT,
141 PNG_FILTER_TYPE_DEFAULT
142 );
143
144 png_write_info(png, info);
145
146 png_bytep* rows = malloc(sizeof(png_bytep) * src.height);
147 for (s32 i = 0; i < src.height; i++)
148 rows[i] = src.data + src.width * i * RGBA_SIZE;
149
150 png_write_image(png, rows);
151 png_write_end(png, NULL);
152
153 png_destroy_write_struct(&png, &info);
154
155 free(rows);
156
157 return stream.buffer;
158}
159
160typedef union
161{
162 struct
163 {
164 u32 bits:8;
165 u32 size:24;
166 };
167
168 u8 data[RGBA_SIZE];
169} Header;
170
171static_assert(sizeof(Header) == RGBA_SIZE, "header_size");
172
173#define BITS_IN_BYTE 8
174#define HEADER_BITS 4
175#define HEADER_SIZE (sizeof(Header) * BITS_IN_BYTE / HEADER_BITS)
176
177static inline void bitcpy(u8* dst, u32 to, const u8* src, u32 from, u32 size)
178{
179 for(s32 i = 0; i < size; i++, to++, from++)
180 BITCHECK(src[from >> 3], from & 7)
181 ? _BITSET(dst[to >> 3], to & 7)
182 : _BITCLEAR(dst[to >> 3], to & 7);
183}
184
185static inline s32 ceildiv(s32 a, s32 b)
186{
187 return (a + b - 1) / b;
188}
189
190png_buffer png_encode(png_buffer cover, png_buffer cart)
191{
192 png_img png = png_read(cover);
193
194 const s32 cartBits = cart.size * BITS_IN_BYTE;
195 const s32 coverSize = png.width * png.height * RGBA_SIZE - HEADER_SIZE;
196 Header header = {CLAMP(ceildiv(cartBits, coverSize), 1, BITS_IN_BYTE), cart.size};
197
198 for (s32 i = 0; i < HEADER_SIZE; i++)
199 bitcpy(png.data, i << 3, header.data, i * HEADER_BITS, HEADER_BITS);
200
201 u8* dst = png.data + HEADER_SIZE;
202 s32 end = ceildiv(cartBits, header.bits);
203 for (s32 i = 0; i < end; i++)
204 bitcpy(dst, i << 3, cart.data, i * header.bits, header.bits);
205
206 for (s32 i = end; i < coverSize; i++)
207 bitcpy(dst, i << 3, (const u8[]){rand()}, 0, header.bits);
208
209 png_buffer out = png_write(png);
210
211 free(png.data);
212
213 return out;
214}
215
216png_buffer png_decode(png_buffer cover)
217{
218 png_img png = png_read(cover);
219
220 if (png.data)
221 {
222 Header header;
223
224 for (s32 i = 0; i < HEADER_SIZE; i++)
225 bitcpy(header.data, i * HEADER_BITS, png.data, i << 3, HEADER_BITS);
226
227 if (header.bits > 0
228 && header.bits <= BITS_IN_BYTE
229 && header.size > 0
230 && header.size <= png.width * png.height * RGBA_SIZE * header.bits / BITS_IN_BYTE - HEADER_SIZE)
231 {
232 s32 aligned = header.size + ceildiv(header.size * BITS_IN_BYTE % header.bits, BITS_IN_BYTE);
233 png_buffer out = { malloc(aligned), header.size };
234
235 const u8* from = png.data + HEADER_SIZE;
236 for (s32 i = 0, end = ceildiv(header.size * BITS_IN_BYTE, header.bits); i < end; i++)
237 bitcpy(out.data, i * header.bits, from, i << 3, header.bits);
238
239 free(png.data);
240
241 return out;
242 }
243 }
244
245 return (png_buffer) { 0 };
246}
247