Skip to content

Commit 4ed1f18

Browse files
committed
Call an encoder written in C using PHP FFI extension
1 parent 8b61306 commit 4ed1f18

File tree

8 files changed

+436
-1
lines changed

8 files changed

+436
-1
lines changed

Readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
It can encode and decode a few megabytes per second with a small memory footprint.
77

88

9+
On x86_64 architectures, it will automatically use the [FFI extension](https://www.php.net/manual/en/book.ffi.php) to encode the file using the provided C library.
10+
11+
912
## Usage
1013

1114
Convert a PNG file using a stream :

src/Codec.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,30 @@
2626

2727
namespace MKCG\Image\QOI;
2828

29+
use MKCG\Image\QOI\FFI\x86;
2930
use MKCG\Image\QOI\Writer\Writer;
3031

31-
final class Codec
32+
class Codec
3233
{
3334
private const BUFFER_SIZE = 1024;
3435

3536
private int $position = 0;
3637

3738
public static function encode(iterable $iterator, ImageDescriptor $descriptor, Writer $writer): void
39+
{
40+
$result = shell_exec('uname -p');
41+
42+
if (is_string($result)) {
43+
$result = trim($result);
44+
}
45+
46+
match ($result) {
47+
"x86_64" => x86::encode($iterator, $descriptor, $writer),
48+
default => static::phpEncode($iterator, $descriptor, $writer),
49+
};
50+
}
51+
52+
private static function phpEncode(iterable $iterator, ImageDescriptor $descriptor, Writer $writer): void
3853
{
3954
$buffer = \FFI::new("unsigned char[" . static::BUFFER_SIZE . "]");
4055
$position = 0;

src/FFI/bin/x86_64.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
3+
This is an adaptation of the Quite OK Image encoder intended to be used as a PHP FFI
4+
5+
@see: https://github.com/phoboslab/qoi
6+
7+
---
8+
9+
QOI - The "Quite OK Image" format for fast, lossless image compression
10+
11+
Dominic Szablewski - https://phoboslab.org
12+
13+
-- LICENSE: The MIT License(MIT)
14+
15+
Copyright(c) 2021 Dominic Szablewski
16+
17+
Permission is hereby granted, free of charge, to any person obtaining a copy of
18+
this software and associated documentation files(the "Software"), to deal in
19+
the Software without restriction, including without limitation the rights to
20+
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
21+
of the Software, and to permit persons to whom the Software is furnished to do
22+
so, subject to the following conditions :
23+
The above copyright notice and this permission notice shall be included in all
24+
copies or substantial portions of the Software.
25+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
28+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31+
SOFTWARE.
32+
33+
*/
34+
35+
#define FFI_SCOPE "PHP_QOI"
36+
#define FFI_LIB "./x86_64.so"
37+
38+
typedef struct {
39+
unsigned int width;
40+
unsigned int height;
41+
unsigned char channels;
42+
unsigned char colorspace;
43+
} qoi_desc;
44+
45+
typedef union {
46+
struct { unsigned char r, g, b, a; } rgba;
47+
unsigned int v;
48+
} qoi_rgba_t;
49+
50+
typedef struct {
51+
int p;
52+
int run;
53+
int px_len;
54+
int px_end;
55+
int px_pos;
56+
qoi_rgba_t index[64];
57+
qoi_rgba_t px_prev;
58+
} qoi_encoder_t;
59+
60+
void qoi_encode_init(qoi_encoder_t *encoder);
61+
void qoi_encode_header(qoi_encoder_t *encoder, unsigned char *output, qoi_desc desc);
62+
int qoi_encode_chunk(qoi_encoder_t *encoder, const unsigned char *input, int length, unsigned char *output, qoi_desc desc);
63+
int qoi_encode_tail(qoi_encoder_t *encoder, unsigned char *output);

src/FFI/bin/x86_64.so

15.4 KB
Binary file not shown.

src/FFI/lib/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build:
2+
gcc qoi.c -O1 -std=gnu99 -shared -lm -o "./../bin/$(shell uname -p).so"

src/FFI/lib/qoi.c

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
3+
This is an adaptation of the Quite OK Image encoder intended to be used as a PHP FFI
4+
5+
@see: https://github.com/phoboslab/qoi
6+
7+
---
8+
9+
QOI - The "Quite OK Image" format for fast, lossless image compression
10+
11+
Dominic Szablewski - https://phoboslab.org
12+
13+
-- LICENSE: The MIT License(MIT)
14+
15+
Copyright(c) 2021 Dominic Szablewski
16+
17+
Permission is hereby granted, free of charge, to any person obtaining a copy of
18+
this software and associated documentation files(the "Software"), to deal in
19+
the Software without restriction, including without limitation the rights to
20+
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
21+
of the Software, and to permit persons to whom the Software is furnished to do
22+
so, subject to the following conditions :
23+
The above copyright notice and this permission notice shall be included in all
24+
copies or substantial portions of the Software.
25+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
28+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31+
SOFTWARE.
32+
33+
*/
34+
35+
#include "qoi.h"
36+
37+
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
38+
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
39+
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
40+
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
41+
#define QOI_OP_RGB 0xfe /* 11111110 */
42+
#define QOI_OP_RGBA 0xff /* 11111111 */
43+
44+
#define QOI_MASK_2 0xc0 /* 11000000 */
45+
46+
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
47+
#define QOI_MAGIC \
48+
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
49+
((unsigned int)'i') << 8 | ((unsigned int)'f'))
50+
#define QOI_HEADER_SIZE 14
51+
52+
#define QOI_PIXELS_MAX ((unsigned int)400000000)
53+
54+
#define QOI_IS_OP_DIFF(vr, vg, vb) (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2)
55+
#define QOI_IS_OP_LUMA(vg, vg_r, vg_b) (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8)
56+
57+
#define QOI_WRITE_OP_DIFF(output, outputPos, vr, vg, vb) \
58+
output[outputPos++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
59+
60+
#define QOI_WRITE_OP_LUMA(output, outputPos, vg, vg_r, vg_b) \
61+
output[outputPos++] = QOI_OP_LUMA | (vg + 32); \
62+
output[outputPos++] = (vg_r + 8) << 4 | (vg_b + 8);
63+
64+
#define QOI_WRITE_OP_RGB(output, outputPos, px) \
65+
output[outputPos++] = QOI_OP_RGB; \
66+
output[outputPos++] = px.rgba.r; \
67+
output[outputPos++] = px.rgba.g; \
68+
output[outputPos++] = px.rgba.b;
69+
70+
#define QOI_WRITE_OP_RGBA(output, outputPos, px) \
71+
output[outputPos++] = QOI_OP_RGBA; \
72+
output[outputPos++] = px.rgba.r; \
73+
output[outputPos++] = px.rgba.g; \
74+
output[outputPos++] = px.rgba.b; \
75+
output[outputPos++] = px.rgba.a;
76+
77+
78+
void qoi_encode_init(qoi_encoder_t *encoder) {
79+
encoder->p = 0;
80+
encoder->run = 0;
81+
encoder->px_len = 0;
82+
encoder->px_end = 0;
83+
encoder->px_pos = 0;
84+
encoder->px_prev.rgba.r = 0;
85+
encoder->px_prev.rgba.g = 0;
86+
encoder->px_prev.rgba.b = 0;
87+
encoder->px_prev.rgba.a = 255;
88+
89+
for (int i = 0; i < 64; i++) {
90+
encoder->index[i].rgba.r = 0;
91+
encoder->index[i].rgba.g = 0;
92+
encoder->index[i].rgba.b = 0;
93+
encoder->index[i].rgba.a = 0;
94+
}
95+
}
96+
97+
static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
98+
bytes[(*p)++] = (0xff000000 & v) >> 24;
99+
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
100+
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
101+
bytes[(*p)++] = (0x000000ff & v);
102+
}
103+
104+
void qoi_encode_header(qoi_encoder_t *encoder, unsigned char *output, qoi_desc desc) {
105+
qoi_write_32(output, &encoder->p, QOI_MAGIC);
106+
qoi_write_32(output, &encoder->p, desc.width);
107+
qoi_write_32(output, &encoder->p, desc.height);
108+
output[encoder->p++] = desc.channels;
109+
output[encoder->p++] = desc.colorspace;
110+
}
111+
112+
int qoi_encode_chunk(qoi_encoder_t *encoder, const unsigned char *input, int length, unsigned char *output, qoi_desc desc) {
113+
qoi_rgba_t px;
114+
115+
int inputPos = 0;
116+
int outputPos = 0;
117+
118+
while (inputPos < length) {
119+
if (desc.channels == 4) {
120+
px = *(qoi_rgba_t *)(input + inputPos);
121+
}
122+
else {
123+
px.rgba.r = input[inputPos + 0];
124+
px.rgba.g = input[inputPos + 1];
125+
px.rgba.b = input[inputPos + 2];
126+
}
127+
128+
if (px.v == encoder->px_prev.v) {
129+
encoder->run++;
130+
131+
if (encoder->run == 62) {
132+
output[outputPos++] = QOI_OP_RUN | (encoder->run - 1);
133+
encoder->run = 0;
134+
}
135+
}
136+
else {
137+
int index_pos;
138+
139+
if (encoder->run > 0) {
140+
output[outputPos++] = QOI_OP_RUN | (encoder->run - 1);
141+
encoder->run = 0;
142+
}
143+
144+
index_pos = QOI_COLOR_HASH(px) % 64;
145+
146+
if (encoder->index[index_pos].v == px.v) {
147+
output[outputPos++] = QOI_OP_INDEX | index_pos;
148+
}
149+
else {
150+
encoder->index[index_pos] = px;
151+
152+
if (px.rgba.a == encoder->px_prev.rgba.a) {
153+
signed char vr = px.rgba.r - encoder->px_prev.rgba.r;
154+
signed char vg = px.rgba.g - encoder->px_prev.rgba.g;
155+
signed char vb = px.rgba.b - encoder->px_prev.rgba.b;
156+
157+
signed char vg_r = vr - vg;
158+
signed char vg_b = vb - vg;
159+
160+
if (QOI_IS_OP_DIFF(vr, vg, vb)) {
161+
QOI_WRITE_OP_DIFF(output, outputPos, vr, vg, vb);
162+
}
163+
else if (QOI_IS_OP_LUMA(vg, vg_r, vg_b)) {
164+
QOI_WRITE_OP_LUMA(output, outputPos, vg, vg_r, vg_b);
165+
}
166+
else {
167+
QOI_WRITE_OP_RGB(output, outputPos, px);
168+
}
169+
}
170+
else {
171+
QOI_WRITE_OP_RGBA(output, outputPos, px);
172+
}
173+
}
174+
}
175+
176+
inputPos += desc.channels;
177+
encoder->px_prev = px;
178+
}
179+
180+
return outputPos;
181+
}
182+
183+
int qoi_encode_tail(qoi_encoder_t *encoder, unsigned char *output) {
184+
int outputPos = 0;
185+
186+
if (encoder->run > 0) {
187+
output[outputPos++] = QOI_OP_RUN | (encoder->run - 1);
188+
encoder->run = 0;
189+
}
190+
191+
output[outputPos++] = 0;
192+
output[outputPos++] = 0;
193+
output[outputPos++] = 0;
194+
output[outputPos++] = 0;
195+
output[outputPos++] = 0;
196+
output[outputPos++] = 0;
197+
output[outputPos++] = 0;
198+
output[outputPos++] = 1;
199+
200+
return outputPos;
201+
}

src/FFI/lib/qoi.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
3+
This is an adaptation of the Quite OK Image encoder intended to be used as a PHP FFI
4+
5+
@see: https://github.com/phoboslab/qoi
6+
7+
---
8+
9+
QOI - The "Quite OK Image" format for fast, lossless image compression
10+
11+
Dominic Szablewski - https://phoboslab.org
12+
13+
-- LICENSE: The MIT License(MIT)
14+
15+
Copyright(c) 2021 Dominic Szablewski
16+
17+
Permission is hereby granted, free of charge, to any person obtaining a copy of
18+
this software and associated documentation files(the "Software"), to deal in
19+
the Software without restriction, including without limitation the rights to
20+
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
21+
of the Software, and to permit persons to whom the Software is furnished to do
22+
so, subject to the following conditions :
23+
The above copyright notice and this permission notice shall be included in all
24+
copies or substantial portions of the Software.
25+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
28+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31+
SOFTWARE.
32+
33+
*/
34+
35+
#ifndef QOI_H
36+
#define QOI_H
37+
38+
typedef struct {
39+
unsigned int width;
40+
unsigned int height;
41+
unsigned char channels;
42+
unsigned char colorspace;
43+
} qoi_desc;
44+
45+
typedef union {
46+
struct { unsigned char r, g, b, a; } rgba;
47+
unsigned int v;
48+
} qoi_rgba_t;
49+
50+
typedef struct {
51+
int p;
52+
int run;
53+
int px_len;
54+
int px_end;
55+
int px_pos;
56+
qoi_rgba_t index[64];
57+
qoi_rgba_t px_prev;
58+
} qoi_encoder_t;
59+
60+
void qoi_encode_init(qoi_encoder_t *encoder);
61+
void qoi_encode_header(qoi_encoder_t *encoder, unsigned char *output, qoi_desc desc);
62+
int qoi_encode_chunk(qoi_encoder_t *encoder, const unsigned char *input, int length, unsigned char *output, qoi_desc desc);
63+
int qoi_encode_tail(qoi_encoder_t *encoder, unsigned char *output);
64+
65+
#endif

0 commit comments

Comments
 (0)