colopresso 12.2.0__cp310-abi3-macosx_10_9_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
colopresso/__init__.py ADDED
@@ -0,0 +1,56 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ #
3
+ # This file is part of colopresso
4
+ #
5
+ # Copyright (C) 2025-2026 COLOPL, Inc.
6
+ #
7
+ # Author: Go Kudo <g-kudo@colopl.co.jp>
8
+ # Developed with AI (LLM) code assistance. See `NOTICE` for details.
9
+
10
+ """
11
+ colopresso - Image compression and color reduction library
12
+ """
13
+
14
+ from .core import (
15
+ Config,
16
+ PngxLossyType,
17
+ encode_webp,
18
+ encode_avif,
19
+ encode_pngx,
20
+ get_version,
21
+ get_libwebp_version,
22
+ get_libpng_version,
23
+ get_libavif_version,
24
+ get_pngx_oxipng_version,
25
+ get_pngx_libimagequant_version,
26
+ get_buildtime,
27
+ get_compiler_version_string,
28
+ get_rust_version_string,
29
+ is_threads_enabled,
30
+ get_default_thread_count,
31
+ get_max_thread_count,
32
+ ColopressoError,
33
+ )
34
+
35
+ __all__ = [
36
+ "Config",
37
+ "PngxLossyType",
38
+ "encode_webp",
39
+ "encode_avif",
40
+ "encode_pngx",
41
+ "get_version",
42
+ "get_libwebp_version",
43
+ "get_libpng_version",
44
+ "get_libavif_version",
45
+ "get_pngx_oxipng_version",
46
+ "get_pngx_libimagequant_version",
47
+ "get_buildtime",
48
+ "get_compiler_version_string",
49
+ "get_rust_version_string",
50
+ "is_threads_enabled",
51
+ "get_default_thread_count",
52
+ "get_max_thread_count",
53
+ "ColopressoError",
54
+ ]
55
+
56
+ __version__ = "12.2.0"
Binary file
@@ -0,0 +1,612 @@
1
+ /*
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * This file is part of colopresso
5
+ *
6
+ * Copyright (C) 2025-2026 COLOPL, Inc.
7
+ *
8
+ * Author: Go Kudo <g-kudo@colopl.co.jp>
9
+ * Developed with AI (LLM) code assistance. See `NOTICE` for details.
10
+ */
11
+
12
+ #define PY_SSIZE_T_CLEAN
13
+ #define Py_LIMITED_API 0x030a0000 /* Python 3.10+ */
14
+
15
+ #include <Python.h>
16
+
17
+ #include <colopresso.h>
18
+ #include <stdbool.h>
19
+ #include <stdint.h>
20
+ #include <stdlib.h>
21
+ #include <string.h>
22
+
23
+ static PyObject *ColopressoError;
24
+
25
+ typedef struct {
26
+ cpres_rgba_color_t *colors;
27
+ int count;
28
+ } protected_colors_t;
29
+
30
+ static inline char *get_utf8_string(PyObject *obj) {
31
+ PyObject *bytes;
32
+ Py_ssize_t len;
33
+ char *result, *str;
34
+
35
+ bytes = PyUnicode_AsEncodedString(obj, "utf-8", "strict");
36
+ if (!bytes) return NULL;
37
+
38
+ if (PyBytes_AsStringAndSize(bytes, &str, &len) < 0) {
39
+ Py_DECREF(bytes);
40
+ return NULL;
41
+ }
42
+
43
+ result = (char *)malloc((size_t)len + 1);
44
+ if (!result) {
45
+ Py_DECREF(bytes);
46
+ PyErr_NoMemory();
47
+ return NULL;
48
+ }
49
+ memcpy(result, str, (size_t)len);
50
+ result[len] = '\0';
51
+
52
+ Py_DECREF(bytes);
53
+ return result;
54
+ }
55
+
56
+ static PyObject *raise_colopresso_error(cpres_error_t err) {
57
+ const char *msg = cpres_error_string(err);
58
+ PyObject *exc = PyObject_CallFunction(ColopressoError, "is", (int)err, msg ? msg : "Unknown error");
59
+ if (exc) {
60
+ PyErr_SetObject(ColopressoError, exc);
61
+ Py_DECREF(exc);
62
+ }
63
+ return NULL;
64
+ }
65
+
66
+ static int parse_protected_colors(PyObject *list, protected_colors_t *pcolors) {
67
+ Py_ssize_t len, i;
68
+ PyObject *item;
69
+ long r, g, b, a;
70
+
71
+ pcolors->colors = NULL;
72
+ pcolors->count = 0;
73
+
74
+ if (list == NULL || list == Py_None) {
75
+ return 0;
76
+ }
77
+
78
+ if (!PyList_Check(list)) {
79
+ PyErr_SetString(PyExc_TypeError, "pngx_protected_colors must be a list");
80
+ return -1;
81
+ }
82
+
83
+ len = PyList_Size(list);
84
+ if (len <= 0) {
85
+ return 0;
86
+ }
87
+ if (len > 256) {
88
+ PyErr_SetString(PyExc_ValueError, "pngx_protected_colors cannot exceed 256 colors");
89
+ return -1;
90
+ }
91
+
92
+ pcolors->colors = (cpres_rgba_color_t *)malloc(sizeof(cpres_rgba_color_t) * (size_t)len);
93
+ if (!pcolors->colors) {
94
+ PyErr_NoMemory();
95
+ return -1;
96
+ }
97
+
98
+ for (i = 0; i < len; i++) {
99
+ item = PyList_GetItem(list, i); /* borrowed reference */
100
+ if (!PyTuple_Check(item) || PyTuple_Size(item) != 4) {
101
+ free(pcolors->colors);
102
+ pcolors->colors = NULL;
103
+ PyErr_SetString(PyExc_TypeError, "Each protected color must be a tuple of (r, g, b, a)");
104
+ return -1;
105
+ }
106
+
107
+ r = PyLong_AsLong(PyTuple_GetItem(item, 0));
108
+ g = PyLong_AsLong(PyTuple_GetItem(item, 1));
109
+ b = PyLong_AsLong(PyTuple_GetItem(item, 2));
110
+ a = PyLong_AsLong(PyTuple_GetItem(item, 3));
111
+
112
+ if (PyErr_Occurred()) {
113
+ free(pcolors->colors);
114
+ pcolors->colors = NULL;
115
+ return -1;
116
+ }
117
+
118
+ if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) {
119
+ free(pcolors->colors);
120
+ pcolors->colors = NULL;
121
+ PyErr_SetString(PyExc_ValueError, "Color component values must be 0-255");
122
+ return -1;
123
+ }
124
+
125
+ pcolors->colors[i].r = (uint8_t)r;
126
+ pcolors->colors[i].g = (uint8_t)g;
127
+ pcolors->colors[i].b = (uint8_t)b;
128
+ pcolors->colors[i].a = (uint8_t)a;
129
+ }
130
+
131
+ pcolors->count = (int)len;
132
+ return 0;
133
+ }
134
+
135
+ static void free_protected_colors(protected_colors_t *pcolors) {
136
+ if (pcolors->colors) {
137
+ free(pcolors->colors);
138
+ pcolors->colors = NULL;
139
+ }
140
+ pcolors->count = 0;
141
+ }
142
+
143
+ static int parse_config(PyObject *config_dict, cpres_config_t *config, protected_colors_t *pcolors) {
144
+ PyObject *key, *value;
145
+ Py_ssize_t pos = 0;
146
+
147
+ cpres_config_init_defaults(config);
148
+ pcolors->colors = NULL;
149
+ pcolors->count = 0;
150
+
151
+ if (config_dict == NULL || config_dict == Py_None) {
152
+ return 0;
153
+ }
154
+
155
+ if (!PyDict_Check(config_dict)) {
156
+ PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
157
+ return -1;
158
+ }
159
+
160
+ while (PyDict_Next(config_dict, &pos, &key, &value)) {
161
+ char *key_str = get_utf8_string(key);
162
+ if (!key_str) {
163
+ free_protected_colors(pcolors);
164
+ return -1;
165
+ }
166
+
167
+ /* WebP */
168
+ if (strcmp(key_str, "webp_quality") == 0) {
169
+ config->webp_quality = (float)PyFloat_AsDouble(value);
170
+ } else if (strcmp(key_str, "webp_lossless") == 0) {
171
+ config->webp_lossless = PyObject_IsTrue(value);
172
+ } else if (strcmp(key_str, "webp_method") == 0) {
173
+ config->webp_method = (int)PyLong_AsLong(value);
174
+ } else if (strcmp(key_str, "webp_target_size") == 0) {
175
+ config->webp_target_size = (int)PyLong_AsLong(value);
176
+ } else if (strcmp(key_str, "webp_target_psnr") == 0) {
177
+ config->webp_target_psnr = (float)PyFloat_AsDouble(value);
178
+ } else if (strcmp(key_str, "webp_segments") == 0) {
179
+ config->webp_segments = (int)PyLong_AsLong(value);
180
+ } else if (strcmp(key_str, "webp_sns_strength") == 0) {
181
+ config->webp_sns_strength = (int)PyLong_AsLong(value);
182
+ } else if (strcmp(key_str, "webp_filter_strength") == 0) {
183
+ config->webp_filter_strength = (int)PyLong_AsLong(value);
184
+ } else if (strcmp(key_str, "webp_filter_sharpness") == 0) {
185
+ config->webp_filter_sharpness = (int)PyLong_AsLong(value);
186
+ } else if (strcmp(key_str, "webp_filter_type") == 0) {
187
+ config->webp_filter_type = (int)PyLong_AsLong(value);
188
+ } else if (strcmp(key_str, "webp_autofilter") == 0) {
189
+ config->webp_autofilter = PyObject_IsTrue(value);
190
+ } else if (strcmp(key_str, "webp_alpha_compression") == 0) {
191
+ config->webp_alpha_compression = PyObject_IsTrue(value);
192
+ } else if (strcmp(key_str, "webp_alpha_filtering") == 0) {
193
+ config->webp_alpha_filtering = (int)PyLong_AsLong(value);
194
+ } else if (strcmp(key_str, "webp_alpha_quality") == 0) {
195
+ config->webp_alpha_quality = (int)PyLong_AsLong(value);
196
+ } else if (strcmp(key_str, "webp_pass") == 0) {
197
+ config->webp_pass = (int)PyLong_AsLong(value);
198
+ } else if (strcmp(key_str, "webp_preprocessing") == 0) {
199
+ config->webp_preprocessing = (int)PyLong_AsLong(value);
200
+ } else if (strcmp(key_str, "webp_partitions") == 0) {
201
+ config->webp_partitions = (int)PyLong_AsLong(value);
202
+ } else if (strcmp(key_str, "webp_partition_limit") == 0) {
203
+ config->webp_partition_limit = (int)PyLong_AsLong(value);
204
+ } else if (strcmp(key_str, "webp_emulate_jpeg_size") == 0) {
205
+ config->webp_emulate_jpeg_size = PyObject_IsTrue(value);
206
+ } else if (strcmp(key_str, "webp_thread_level") == 0) {
207
+ config->webp_thread_level = (int)PyLong_AsLong(value);
208
+ } else if (strcmp(key_str, "webp_low_memory") == 0) {
209
+ config->webp_low_memory = PyObject_IsTrue(value);
210
+ } else if (strcmp(key_str, "webp_near_lossless") == 0) {
211
+ config->webp_near_lossless = (int)PyLong_AsLong(value);
212
+ } else if (strcmp(key_str, "webp_exact") == 0) {
213
+ config->webp_exact = PyObject_IsTrue(value);
214
+ } else if (strcmp(key_str, "webp_use_delta_palette") == 0) {
215
+ config->webp_use_delta_palette = PyObject_IsTrue(value);
216
+ } else if (strcmp(key_str, "webp_use_sharp_yuv") == 0) {
217
+ config->webp_use_sharp_yuv = PyObject_IsTrue(value);
218
+ }
219
+
220
+ /* AVIF */
221
+ else if (strcmp(key_str, "avif_quality") == 0) {
222
+ config->avif_quality = (float)PyFloat_AsDouble(value);
223
+ } else if (strcmp(key_str, "avif_alpha_quality") == 0) {
224
+ config->avif_alpha_quality = (int)PyLong_AsLong(value);
225
+ } else if (strcmp(key_str, "avif_lossless") == 0) {
226
+ config->avif_lossless = PyObject_IsTrue(value);
227
+ } else if (strcmp(key_str, "avif_speed") == 0) {
228
+ config->avif_speed = (int)PyLong_AsLong(value);
229
+ } else if (strcmp(key_str, "avif_threads") == 0) {
230
+ config->avif_threads = (int)PyLong_AsLong(value);
231
+ }
232
+
233
+ /* PNGX (PNG) */
234
+ else if (strcmp(key_str, "pngx_level") == 0) {
235
+ config->pngx_level = (int)PyLong_AsLong(value);
236
+ } else if (strcmp(key_str, "pngx_strip_safe") == 0) {
237
+ config->pngx_strip_safe = PyObject_IsTrue(value);
238
+ } else if (strcmp(key_str, "pngx_optimize_alpha") == 0) {
239
+ config->pngx_optimize_alpha = PyObject_IsTrue(value);
240
+ } else if (strcmp(key_str, "pngx_lossy_enable") == 0) {
241
+ config->pngx_lossy_enable = PyObject_IsTrue(value);
242
+ } else if (strcmp(key_str, "pngx_lossy_type") == 0) {
243
+ config->pngx_lossy_type = (int)PyLong_AsLong(value);
244
+ } else if (strcmp(key_str, "pngx_lossy_max_colors") == 0) {
245
+ config->pngx_lossy_max_colors = (int)PyLong_AsLong(value);
246
+ } else if (strcmp(key_str, "pngx_lossy_reduced_colors") == 0) {
247
+ config->pngx_lossy_reduced_colors = (int)PyLong_AsLong(value);
248
+ } else if (strcmp(key_str, "pngx_lossy_reduced_bits_rgb") == 0) {
249
+ config->pngx_lossy_reduced_bits_rgb = (int)PyLong_AsLong(value);
250
+ } else if (strcmp(key_str, "pngx_lossy_reduced_alpha_bits") == 0) {
251
+ config->pngx_lossy_reduced_alpha_bits = (int)PyLong_AsLong(value);
252
+ } else if (strcmp(key_str, "pngx_lossy_quality_min") == 0) {
253
+ config->pngx_lossy_quality_min = (int)PyLong_AsLong(value);
254
+ } else if (strcmp(key_str, "pngx_lossy_quality_max") == 0) {
255
+ config->pngx_lossy_quality_max = (int)PyLong_AsLong(value);
256
+ } else if (strcmp(key_str, "pngx_lossy_speed") == 0) {
257
+ config->pngx_lossy_speed = (int)PyLong_AsLong(value);
258
+ } else if (strcmp(key_str, "pngx_lossy_dither_level") == 0) {
259
+ config->pngx_lossy_dither_level = (float)PyFloat_AsDouble(value);
260
+ } else if (strcmp(key_str, "pngx_saliency_map_enable") == 0) {
261
+ config->pngx_saliency_map_enable = PyObject_IsTrue(value);
262
+ } else if (strcmp(key_str, "pngx_chroma_anchor_enable") == 0) {
263
+ config->pngx_chroma_anchor_enable = PyObject_IsTrue(value);
264
+ } else if (strcmp(key_str, "pngx_adaptive_dither_enable") == 0) {
265
+ config->pngx_adaptive_dither_enable = PyObject_IsTrue(value);
266
+ } else if (strcmp(key_str, "pngx_gradient_boost_enable") == 0) {
267
+ config->pngx_gradient_boost_enable = PyObject_IsTrue(value);
268
+ } else if (strcmp(key_str, "pngx_chroma_weight_enable") == 0) {
269
+ config->pngx_chroma_weight_enable = PyObject_IsTrue(value);
270
+ } else if (strcmp(key_str, "pngx_postprocess_smooth_enable") == 0) {
271
+ config->pngx_postprocess_smooth_enable = PyObject_IsTrue(value);
272
+ } else if (strcmp(key_str, "pngx_postprocess_smooth_importance_cutoff") == 0) {
273
+ config->pngx_postprocess_smooth_importance_cutoff = (float)PyFloat_AsDouble(value);
274
+ } else if (strcmp(key_str, "pngx_palette256_gradient_profile_enable") == 0) {
275
+ config->pngx_palette256_gradient_profile_enable = PyObject_IsTrue(value);
276
+ } else if (strcmp(key_str, "pngx_palette256_gradient_dither_floor") == 0) {
277
+ config->pngx_palette256_gradient_dither_floor = (float)PyFloat_AsDouble(value);
278
+ } else if (strcmp(key_str, "pngx_palette256_alpha_bleed_enable") == 0) {
279
+ config->pngx_palette256_alpha_bleed_enable = PyObject_IsTrue(value);
280
+ } else if (strcmp(key_str, "pngx_palette256_alpha_bleed_max_distance") == 0) {
281
+ config->pngx_palette256_alpha_bleed_max_distance = (int)PyLong_AsLong(value);
282
+ } else if (strcmp(key_str, "pngx_palette256_alpha_bleed_opaque_threshold") == 0) {
283
+ config->pngx_palette256_alpha_bleed_opaque_threshold = (int)PyLong_AsLong(value);
284
+ } else if (strcmp(key_str, "pngx_palette256_alpha_bleed_soft_limit") == 0) {
285
+ config->pngx_palette256_alpha_bleed_soft_limit = (int)PyLong_AsLong(value);
286
+ } else if (strcmp(key_str, "pngx_palette256_profile_opaque_ratio_threshold") == 0) {
287
+ config->pngx_palette256_profile_opaque_ratio_threshold = (float)PyFloat_AsDouble(value);
288
+ } else if (strcmp(key_str, "pngx_palette256_profile_gradient_mean_max") == 0) {
289
+ config->pngx_palette256_profile_gradient_mean_max = (float)PyFloat_AsDouble(value);
290
+ } else if (strcmp(key_str, "pngx_palette256_profile_saturation_mean_max") == 0) {
291
+ config->pngx_palette256_profile_saturation_mean_max = (float)PyFloat_AsDouble(value);
292
+ } else if (strcmp(key_str, "pngx_palette256_tune_opaque_ratio_threshold") == 0) {
293
+ config->pngx_palette256_tune_opaque_ratio_threshold = (float)PyFloat_AsDouble(value);
294
+ } else if (strcmp(key_str, "pngx_palette256_tune_gradient_mean_max") == 0) {
295
+ config->pngx_palette256_tune_gradient_mean_max = (float)PyFloat_AsDouble(value);
296
+ } else if (strcmp(key_str, "pngx_palette256_tune_saturation_mean_max") == 0) {
297
+ config->pngx_palette256_tune_saturation_mean_max = (float)PyFloat_AsDouble(value);
298
+ } else if (strcmp(key_str, "pngx_palette256_tune_speed_max") == 0) {
299
+ config->pngx_palette256_tune_speed_max = (int)PyLong_AsLong(value);
300
+ } else if (strcmp(key_str, "pngx_palette256_tune_quality_min_floor") == 0) {
301
+ config->pngx_palette256_tune_quality_min_floor = (int)PyLong_AsLong(value);
302
+ } else if (strcmp(key_str, "pngx_palette256_tune_quality_max_target") == 0) {
303
+ config->pngx_palette256_tune_quality_max_target = (int)PyLong_AsLong(value);
304
+ } else if (strcmp(key_str, "pngx_threads") == 0) {
305
+ config->pngx_threads = (int)PyLong_AsLong(value);
306
+ } else if (strcmp(key_str, "pngx_protected_colors") == 0) {
307
+ free(key_str);
308
+ free_protected_colors(pcolors);
309
+ if (parse_protected_colors(value, pcolors) < 0) {
310
+ return -1;
311
+ }
312
+ config->pngx_protected_colors = pcolors->colors;
313
+ config->pngx_protected_colors_count = pcolors->count;
314
+ continue;
315
+ }
316
+
317
+ free(key_str);
318
+
319
+ if (PyErr_Occurred()) {
320
+ free_protected_colors(pcolors);
321
+ return -1;
322
+ }
323
+ }
324
+
325
+ return 0;
326
+ }
327
+
328
+ static PyObject *py_encode_webp(PyObject *self, PyObject *args, PyObject *kwargs) {
329
+ static char *kwlist[] = {"png_data", "config", NULL};
330
+ PyObject *config_dict = Py_None, *result, *png_obj;
331
+ Py_ssize_t png_size;
332
+ cpres_config_t config;
333
+ cpres_error_t err;
334
+ protected_colors_t pcolors = {NULL, 0};
335
+ uint8_t *out_data = NULL;
336
+ size_t out_size = 0;
337
+ char *png_data;
338
+
339
+ (void)self;
340
+
341
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &png_obj, &config_dict)) {
342
+ return NULL;
343
+ }
344
+
345
+ if (!PyBytes_Check(png_obj)) {
346
+ PyErr_SetString(PyExc_TypeError, "png_data must be bytes");
347
+ return NULL;
348
+ }
349
+
350
+ if (PyBytes_AsStringAndSize(png_obj, &png_data, &png_size) < 0) {
351
+ return NULL;
352
+ }
353
+
354
+ if (parse_config(config_dict, &config, &pcolors) < 0) {
355
+ free_protected_colors(&pcolors);
356
+ return NULL;
357
+ }
358
+
359
+ Py_BEGIN_ALLOW_THREADS
360
+ err = cpres_encode_webp_memory((const uint8_t *)png_data, (size_t)png_size,
361
+ &out_data, &out_size, &config);
362
+ Py_END_ALLOW_THREADS
363
+
364
+ free_protected_colors(&pcolors);
365
+
366
+ if (err != CPRES_OK) {
367
+ return raise_colopresso_error(err);
368
+ }
369
+
370
+ result = PyBytes_FromStringAndSize((const char *)out_data, (Py_ssize_t)out_size);
371
+ cpres_free(out_data);
372
+ return result;
373
+ }
374
+
375
+ static PyObject *py_encode_avif(PyObject *self, PyObject *args, PyObject *kwargs) {
376
+ static char *kwlist[] = {"png_data", "config", NULL};
377
+ PyObject *config_dict = Py_None, *png_obj, *result;
378
+ Py_ssize_t png_size;
379
+ cpres_config_t config;
380
+ cpres_error_t err;
381
+ protected_colors_t pcolors = {NULL, 0};
382
+ uint8_t *out_data = NULL;
383
+ size_t out_size = 0;
384
+ char *png_data;
385
+
386
+ (void)self;
387
+
388
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &png_obj, &config_dict)) {
389
+ return NULL;
390
+ }
391
+
392
+ if (!PyBytes_Check(png_obj)) {
393
+ PyErr_SetString(PyExc_TypeError, "png_data must be bytes");
394
+ return NULL;
395
+ }
396
+
397
+ if (PyBytes_AsStringAndSize(png_obj, &png_data, &png_size) < 0) {
398
+ return NULL;
399
+ }
400
+
401
+ if (parse_config(config_dict, &config, &pcolors) < 0) {
402
+ free_protected_colors(&pcolors);
403
+ return NULL;
404
+ }
405
+
406
+ Py_BEGIN_ALLOW_THREADS
407
+ err = cpres_encode_avif_memory((const uint8_t *)png_data, (size_t)png_size,
408
+ &out_data, &out_size, &config);
409
+ Py_END_ALLOW_THREADS
410
+
411
+ free_protected_colors(&pcolors);
412
+
413
+ if (err != CPRES_OK) {
414
+ return raise_colopresso_error(err);
415
+ }
416
+
417
+ result = PyBytes_FromStringAndSize((const char *)out_data, (Py_ssize_t)out_size);
418
+ cpres_free(out_data);
419
+ return result;
420
+ }
421
+
422
+ static PyObject *py_encode_pngx(PyObject *self, PyObject *args, PyObject *kwargs) {
423
+ static char *kwlist[] = {"png_data", "config", NULL};
424
+ PyObject *config_dict = Py_None, *png_obj, *result;
425
+ Py_ssize_t png_size;
426
+ cpres_config_t config;
427
+ cpres_error_t err;
428
+ protected_colors_t pcolors = {NULL, 0};
429
+ uint8_t *out_data = NULL;
430
+ size_t out_size = 0;
431
+ char *png_data;
432
+
433
+ (void)self;
434
+
435
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, &png_obj, &config_dict)) {
436
+ return NULL;
437
+ }
438
+
439
+ if (!PyBytes_Check(png_obj)) {
440
+ PyErr_SetString(PyExc_TypeError, "png_data must be bytes");
441
+ return NULL;
442
+ }
443
+
444
+ if (PyBytes_AsStringAndSize(png_obj, &png_data, &png_size) < 0) {
445
+ return NULL;
446
+ }
447
+
448
+ if (parse_config(config_dict, &config, &pcolors) < 0) {
449
+ free_protected_colors(&pcolors);
450
+ return NULL;
451
+ }
452
+
453
+ Py_BEGIN_ALLOW_THREADS
454
+ err = cpres_encode_pngx_memory((const uint8_t *)png_data, (size_t)png_size,
455
+ &out_data, &out_size, &config);
456
+ Py_END_ALLOW_THREADS
457
+
458
+ free_protected_colors(&pcolors);
459
+
460
+ if (err != CPRES_OK) {
461
+ return raise_colopresso_error(err);
462
+ }
463
+
464
+ result = PyBytes_FromStringAndSize((const char *)out_data, (Py_ssize_t)out_size);
465
+ cpres_free(out_data);
466
+ return result;
467
+ }
468
+
469
+ static PyObject *py_get_version(PyObject *self, PyObject *Py_UNUSED(args)) {
470
+ (void)self;
471
+ return PyLong_FromUnsignedLong(cpres_get_version());
472
+ }
473
+
474
+ static PyObject *py_get_libwebp_version(PyObject *self, PyObject *Py_UNUSED(args)) {
475
+ (void)self;
476
+ return PyLong_FromUnsignedLong(cpres_get_libwebp_version());
477
+ }
478
+
479
+ static PyObject *py_get_libpng_version(PyObject *self, PyObject *Py_UNUSED(args)) {
480
+ (void)self;
481
+ return PyLong_FromUnsignedLong(cpres_get_libpng_version());
482
+ }
483
+
484
+ static PyObject *py_get_libavif_version(PyObject *self, PyObject *Py_UNUSED(args)) {
485
+ (void)self;
486
+ return PyLong_FromUnsignedLong(cpres_get_libavif_version());
487
+ }
488
+
489
+ static PyObject *py_get_pngx_oxipng_version(PyObject *self, PyObject *Py_UNUSED(args)) {
490
+ (void)self;
491
+ return PyLong_FromUnsignedLong(cpres_get_pngx_oxipng_version());
492
+ }
493
+
494
+ static PyObject *py_get_pngx_libimagequant_version(PyObject *self, PyObject *Py_UNUSED(args)) {
495
+ (void)self;
496
+ return PyLong_FromUnsignedLong(cpres_get_pngx_libimagequant_version());
497
+ }
498
+
499
+ static PyObject *py_get_buildtime(PyObject *self, PyObject *Py_UNUSED(args)) {
500
+ (void)self;
501
+ return PyLong_FromUnsignedLong(cpres_get_buildtime());
502
+ }
503
+
504
+ static PyObject *py_get_compiler_version_string(PyObject *self, PyObject *Py_UNUSED(args)) {
505
+ const char *s;
506
+ (void)self;
507
+ s = cpres_get_compiler_version_string();
508
+ return PyUnicode_FromString(s ? s : "");
509
+ }
510
+
511
+ static PyObject *py_get_rust_version_string(PyObject *self, PyObject *Py_UNUSED(args)) {
512
+ const char *s;
513
+ (void)self;
514
+ s = cpres_get_rust_version_string();
515
+ return PyUnicode_FromString(s ? s : "");
516
+ }
517
+
518
+ static PyObject *py_is_threads_enabled(PyObject *self, PyObject *Py_UNUSED(args)) {
519
+ (void)self;
520
+ return PyBool_FromLong(cpres_is_threads_enabled());
521
+ }
522
+
523
+ static PyObject *py_get_default_thread_count(PyObject *self, PyObject *Py_UNUSED(args)) {
524
+ (void)self;
525
+ return PyLong_FromUnsignedLong(cpres_get_default_thread_count());
526
+ }
527
+
528
+ static PyObject *py_get_max_thread_count(PyObject *self, PyObject *Py_UNUSED(args)) {
529
+ (void)self;
530
+ return PyLong_FromUnsignedLong(cpres_get_max_thread_count());
531
+ }
532
+
533
+ static PyMethodDef colopresso_methods[] = {
534
+ {"encode_webp", (PyCFunction)py_encode_webp, METH_VARARGS | METH_KEYWORDS,
535
+ "Encode PNG data to WebP format.\n\n"
536
+ "Args:\n"
537
+ " png_data: Raw PNG file data (bytes)\n"
538
+ " config: Optional configuration dictionary\n\n"
539
+ "Returns:\n"
540
+ " WebP encoded data (bytes)"},
541
+ {"encode_avif", (PyCFunction)py_encode_avif, METH_VARARGS | METH_KEYWORDS,
542
+ "Encode PNG data to AVIF format.\n\n"
543
+ "Args:\n"
544
+ " png_data: Raw PNG file data (bytes)\n"
545
+ " config: Optional configuration dictionary\n\n"
546
+ "Returns:\n"
547
+ " AVIF encoded data (bytes)"},
548
+ {"encode_pngx", (PyCFunction)py_encode_pngx, METH_VARARGS | METH_KEYWORDS,
549
+ "Optimize PNG data using PNGX encoder.\n\n"
550
+ "Args:\n"
551
+ " png_data: Raw PNG file data (bytes)\n"
552
+ " config: Optional configuration dictionary\n\n"
553
+ "Returns:\n"
554
+ " Optimized PNG data (bytes)"},
555
+ {"get_version", py_get_version, METH_NOARGS, "Get colopresso version number"},
556
+ {"get_libwebp_version", py_get_libwebp_version, METH_NOARGS, "Get libwebp version number"},
557
+ {"get_libpng_version", py_get_libpng_version, METH_NOARGS, "Get libpng version number"},
558
+ {"get_libavif_version", py_get_libavif_version, METH_NOARGS, "Get libavif version number"},
559
+ {"get_pngx_oxipng_version", py_get_pngx_oxipng_version, METH_NOARGS, "Get oxipng version number"},
560
+ {"get_pngx_libimagequant_version", py_get_pngx_libimagequant_version, METH_NOARGS, "Get libimagequant version number"},
561
+ {"get_buildtime", py_get_buildtime, METH_NOARGS, "Get build timestamp"},
562
+ {"get_compiler_version_string", py_get_compiler_version_string, METH_NOARGS, "Get compiler version string"},
563
+ {"get_rust_version_string", py_get_rust_version_string, METH_NOARGS, "Get Rust version string"},
564
+ {"is_threads_enabled", py_is_threads_enabled, METH_NOARGS, "Check if threading is enabled"},
565
+ {"get_default_thread_count", py_get_default_thread_count, METH_NOARGS, "Get default thread count"},
566
+ {"get_max_thread_count", py_get_max_thread_count, METH_NOARGS, "Get maximum thread count"},
567
+ {NULL, NULL, 0, NULL}
568
+ };
569
+
570
+ static struct PyModuleDef colopresso_module = {
571
+ PyModuleDef_HEAD_INIT,
572
+ "_colopresso",
573
+ "colopresso image compression library",
574
+ -1,
575
+ colopresso_methods,
576
+ NULL,
577
+ NULL,
578
+ NULL,
579
+ NULL
580
+ };
581
+
582
+ PyMODINIT_FUNC PyInit__colopresso(void) {
583
+ PyObject *m;
584
+
585
+ m = PyModule_Create(&colopresso_module);
586
+ if (m == NULL) {
587
+ return NULL;
588
+ }
589
+
590
+ ColopressoError = PyErr_NewException("_colopresso.ColopressoError", NULL, NULL);
591
+ if (ColopressoError == NULL) {
592
+ Py_DECREF(m);
593
+ return NULL;
594
+ }
595
+
596
+ Py_INCREF(ColopressoError);
597
+
598
+ if (PyModule_AddObject(m, "ColopressoError", ColopressoError) < 0) {
599
+ Py_DECREF(ColopressoError);
600
+ Py_DECREF(m);
601
+ return NULL;
602
+ }
603
+
604
+ if (PyModule_AddIntConstant(m, "PNGX_LOSSY_TYPE_PALETTE256", COLOPRESSO_PNGX_LOSSY_TYPE_PALETTE256) < 0 ||
605
+ PyModule_AddIntConstant(m, "PNGX_LOSSY_TYPE_LIMITED_RGBA4444", COLOPRESSO_PNGX_LOSSY_TYPE_LIMITED_RGBA4444) < 0 ||
606
+ PyModule_AddIntConstant(m, "PNGX_LOSSY_TYPE_REDUCED_RGBA32", COLOPRESSO_PNGX_LOSSY_TYPE_REDUCED_RGBA32) < 0) {
607
+ Py_DECREF(m);
608
+ return NULL;
609
+ }
610
+
611
+ return m;
612
+ }