1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved
3 * A portion of this file is copied and altered from the QOI projects' source,
4 * Originally written by Dominic Szablewski. It is slightly modified.
5 * For the original unaltered QOI header, you can find it here:
6 * https://github.com/phoboslab/qoi/blob/master/qoi.h
8 * Copyright (C) 2021, Dominic Szablewski
9 * SPDX-License-Identifier: MIT
12 Copyright (c) 2022 Dominic Szablewski - https://phoboslab.org
14 Permission is hereby granted, free of charge, to any person obtaining a copy
15 of this software and associated documentation files (the "Software"), to deal
16 in the Software without restriction, including without limitation the rights
17 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 copies of the Software, and to permit persons to whom the Software is
19 furnished to do so, subject to the following conditions:
21 The above copyright notice and this permission notice shall be included in all
22 copies or substantial portions of the Software.
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38 #include "vg/vg_log.h"
40 #define STB_IMAGE_WRITE_IMPLEMENTATION
41 #include "vg/submodules/stb/stb_image_write.h"
45 #define STBI_MALLOC(X)
46 #define STBI_REALLOC(X,Y)
50 #define STBI_ONLY_JPEG
51 #define STB_IMAGE_IMPLEMENTATION
52 #include "vg/submodules/stb/stb_image.h"
59 #define VG_TEX2D_LINEAR 0x1
60 #define VG_TEX2D_NEAREST 0x2
61 #define VG_TEX2D_REPEAT 0x4
62 #define VG_TEX2D_CLAMP 0x8
63 #define VG_TEX2D_NOMIP 0x10
65 static u8 const_vg_tex2d_err
[] =
67 0x00, 0xff, 0xff, 0xff,
68 0xff, 0xff, 0x00, 0xff,
69 0x00, 0x00, 0xff, 0xff,
70 0xff, 0xff, 0x00, 0xff
79 unsigned char channels
;
80 unsigned char colorspace
;
84 #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
87 #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
88 #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
89 #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
90 #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
91 #define QOI_OP_RGB 0xfe /* 11111110 */
92 #define QOI_OP_RGBA 0xff /* 11111111 */
94 #define QOI_MASK_2 0xc0 /* 11000000 */
96 #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
98 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
99 ((unsigned int)'i') << 8 | ((unsigned int)'f'))
100 #define QOI_HEADER_SIZE 14
102 /* 2GB is the max file size that this implementation can safely handle. We guard
103 against anything larger than that, assuming the worst case with 5 bytes per
104 pixel, rounded down to a nice clean value. 400 million pixels ought to be
105 enough for anybody. */
106 #define QOI_PIXELS_MAX ((unsigned int)400000000)
109 struct { unsigned char r
, g
, b
, a
; } rgba
;
113 static const unsigned char qoi_padding
[8] = {0,0,0,0,0,0,0,1};
114 static u32
qoi_read_32( const u8
*bytes
, int *p
) {
115 u32 a
= bytes
[(*p
)++];
116 u32 b
= bytes
[(*p
)++];
117 u32 c
= bytes
[(*p
)++];
118 u32 d
= bytes
[(*p
)++];
119 return a
<< 24 | b
<< 16 | c
<< 8 | d
;
122 struct texture_load_info
{
124 u32 width
, height
, flags
;
128 VG_STATIC
void async_vg_tex2d_upload( void *payload
, u32 size
)
130 struct texture_load_info
*info
= payload
;
132 glGenTextures( 1, info
->dest
);
133 glBindTexture( GL_TEXTURE_2D
, *info
->dest
);
134 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGBA
, info
->width
, info
->height
,
135 0, GL_RGBA
, GL_UNSIGNED_BYTE
, info
->rgba
);
137 if( !(info
->flags
& VG_TEX2D_NOMIP
) ){
138 glGenerateMipmap( GL_TEXTURE_2D
);
141 if( info
->flags
& VG_TEX2D_LINEAR
){
142 if( info
->flags
& VG_TEX2D_NOMIP
){
143 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
146 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
,
147 GL_LINEAR_MIPMAP_LINEAR
);
149 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
152 if( info
->flags
& VG_TEX2D_NEAREST
){
153 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_NEAREST
);
154 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_NEAREST
);
157 if( info
->flags
& VG_TEX2D_CLAMP
){
158 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_CLAMP_TO_EDGE
);
159 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_CLAMP_TO_EDGE
);
162 if( info
->flags
& VG_TEX2D_REPEAT
){
163 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, GL_REPEAT
);
164 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, GL_REPEAT
);
168 VG_STATIC
void vg_tex2d_replace_with_error( GLuint
*dest
)
170 u32 hdr_size
= vg_align8(sizeof(struct texture_load_info
));
172 vg_async_item
*call
= vg_async_alloc( hdr_size
);
173 struct texture_load_info
*info
= call
->payload
;
176 info
->flags
= VG_TEX2D_NEAREST
|VG_TEX2D_REPEAT
|VG_TEX2D_NOMIP
;
179 info
->rgba
= const_vg_tex2d_err
;
181 vg_async_dispatch( call
, async_vg_tex2d_upload
);
185 void vg_tex2d_load_qoi_async( const u8
*bytes
, u32 size
,
186 u32 flags
, GLuint
*dest
)
189 qoi_rgba_t index
[64];
191 int px_len
, chunks_len
, px_pos
;
194 u32 channels
= 4; /* TODO */
200 (channels
!= 0 && channels
!= 3 && channels
!= 4) ||
201 size
< QOI_HEADER_SIZE
+ (int)sizeof(qoi_padding
)
203 vg_error( "Error while decoding qoi file: illegal parameters\n" );
204 vg_tex2d_replace_with_error( dest
);
208 header_magic
= qoi_read_32(bytes
, &p
);
209 desc
.width
= qoi_read_32(bytes
, &p
);
210 desc
.height
= qoi_read_32(bytes
, &p
);
211 desc
.channels
= bytes
[p
++];
212 desc
.colorspace
= bytes
[p
++];
215 desc
.width
== 0 || desc
.height
== 0 ||
216 desc
.channels
< 3 || desc
.channels
> 4 ||
217 desc
.colorspace
> 1 ||
218 header_magic
!= QOI_MAGIC
||
219 desc
.height
>= QOI_PIXELS_MAX
/ desc
.width
221 vg_error( "Error while decoding qoi file: invalid file\n" );
222 vg_tex2d_replace_with_error( dest
);
227 channels
= desc
.channels
;
230 px_len
= desc
.width
* desc
.height
* channels
;
232 /* allocate async call
233 * --------------------------
235 u32 hdr_size
= vg_align8(sizeof(struct texture_load_info
)),
236 tex_size
= vg_align8(px_len
);
238 vg_async_item
*call
= vg_async_alloc( hdr_size
+ tex_size
);
239 struct texture_load_info
*info
= call
->payload
;
243 info
->width
= desc
.width
;
244 info
->height
= desc
.height
;
245 info
->rgba
= ((u8
*)call
->payload
) + hdr_size
;
249 * ----------------------------
252 u8
*pixels
= info
->rgba
;
260 chunks_len
= size
- (int)sizeof(qoi_padding
);
261 for (px_pos
= 0; px_pos
< px_len
; px_pos
+= channels
) {
265 else if (p
< chunks_len
) {
268 if (b1
== QOI_OP_RGB
) {
269 px
.rgba
.r
= bytes
[p
++];
270 px
.rgba
.g
= bytes
[p
++];
271 px
.rgba
.b
= bytes
[p
++];
273 else if (b1
== QOI_OP_RGBA
) {
274 px
.rgba
.r
= bytes
[p
++];
275 px
.rgba
.g
= bytes
[p
++];
276 px
.rgba
.b
= bytes
[p
++];
277 px
.rgba
.a
= bytes
[p
++];
279 else if ((b1
& QOI_MASK_2
) == QOI_OP_INDEX
) {
282 else if ((b1
& QOI_MASK_2
) == QOI_OP_DIFF
) {
283 px
.rgba
.r
+= ((b1
>> 4) & 0x03) - 2;
284 px
.rgba
.g
+= ((b1
>> 2) & 0x03) - 2;
285 px
.rgba
.b
+= ( b1
& 0x03) - 2;
287 else if ((b1
& QOI_MASK_2
) == QOI_OP_LUMA
) {
289 int vg
= (b1
& 0x3f) - 32;
290 px
.rgba
.r
+= vg
- 8 + ((b2
>> 4) & 0x0f);
292 px
.rgba
.b
+= vg
- 8 + (b2
& 0x0f);
294 else if ((b1
& QOI_MASK_2
) == QOI_OP_RUN
) {
298 index
[QOI_COLOR_HASH(px
) % 64] = px
;
301 pixels
[px_pos
+ 0] = px
.rgba
.r
;
302 pixels
[px_pos
+ 1] = px
.rgba
.g
;
303 pixels
[px_pos
+ 2] = px
.rgba
.b
;
306 pixels
[px_pos
+ 3] = px
.rgba
.a
;
312 * --------------------------
315 vg_async_dispatch( call
, async_vg_tex2d_upload
);
319 void vg_tex2d_load_qoi_async_file( const char *path
, u32 flags
, GLuint
*dest
)
321 vg_linear_clear( vg_mem
.scratch
);
324 const void *data
= vg_file_read( vg_mem
.scratch
, path
, &size
);
325 vg_tex2d_load_qoi_async( data
, size
, flags
, dest
);
328 #endif /* VG_TEX_H */