Line data Source code
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 : #include <errno.h>
13 : #include <math.h>
14 : #include <stdbool.h>
15 : #include <stdint.h>
16 : #include <stdio.h>
17 : #include <stdlib.h>
18 : #include <string.h>
19 :
20 : #include <avif/avif.h>
21 :
22 : #include <colopresso.h>
23 :
24 : #include "internal/avif.h"
25 : #include "internal/log.h"
26 :
27 : static int g_avif_last_error = 0;
28 :
29 17 : static inline void apply_avif_config(avifEncoder *encoder, const cpres_config_t *config)
30 : {
31 : int alpha_quality, threads, speed;
32 : float quality;
33 :
34 17 : if (!encoder || !config) {
35 0 : return;
36 : }
37 :
38 17 : quality = config->avif_quality;
39 17 : alpha_quality = config->avif_alpha_quality;
40 17 : threads = config->avif_threads;
41 :
42 17 : if (quality < 0) {
43 1 : quality = 0;
44 16 : } else if (quality > 100) {
45 1 : quality = 100;
46 : }
47 :
48 17 : if (alpha_quality < 0) {
49 1 : alpha_quality = 0;
50 16 : } else if (alpha_quality > 100) {
51 1 : alpha_quality = 100;
52 : }
53 :
54 17 : encoder->quality = quality;
55 17 : encoder->qualityAlpha = alpha_quality;
56 :
57 17 : if (threads > 0) {
58 17 : encoder->maxThreads = threads;
59 : }
60 :
61 17 : speed = config->avif_speed;
62 17 : if (speed < 0) {
63 1 : speed = 0;
64 16 : } else if (speed > 10) {
65 1 : speed = 10;
66 : }
67 :
68 17 : encoder->speed = speed;
69 :
70 17 : if (config->avif_lossless) {
71 3 : encoder->quality = AVIF_QUALITY_LOSSLESS;
72 3 : encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
73 : }
74 : }
75 :
76 18 : static inline avifImage *create_avif_image_from_rgba(uint8_t *rgba_data, uint32_t width,
77 : uint32_t height)
78 : {
79 18 : avifImage *image = NULL;
80 : avifRGBImage rgb;
81 :
82 18 : if (!rgba_data || width == 0 || height == 0) {
83 1 : return image;
84 : }
85 :
86 17 : image = avifImageCreate((uint32_t)width, (uint32_t)height, 8, AVIF_PIXEL_FORMAT_YUV444);
87 17 : if (!image) {
88 0 : return NULL;
89 : }
90 :
91 17 : avifRGBImageSetDefaults(&rgb, image);
92 17 : rgb.format = AVIF_RGB_FORMAT_RGBA;
93 17 : rgb.depth = 8;
94 17 : rgb.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC;
95 17 : rgb.pixels = rgba_data;
96 17 : rgb.rowBytes = width * 4;
97 :
98 17 : if (avifImageRGBToYUV(image, &rgb) != AVIF_RESULT_OK) {
99 0 : avifImageDestroy(image);
100 :
101 0 : return NULL;
102 : }
103 :
104 17 : return image;
105 : }
106 :
107 18 : static inline cpres_error_t encode_avif_common(uint8_t *rgba_data, uint32_t width, uint32_t height,
108 : avifRWData *output, const cpres_config_t *config)
109 : {
110 18 : avifImage *image = NULL;
111 18 : avifEncoder *encoder = NULL;
112 : avifResult result;
113 :
114 18 : if (!rgba_data || !output || !config) {
115 0 : return CPRES_ERROR_INVALID_PARAMETER;
116 : }
117 :
118 18 : image = create_avif_image_from_rgba(rgba_data, width, height);
119 18 : if (!image) {
120 1 : avif_set_last_error(AVIF_RESULT_OUT_OF_MEMORY);
121 :
122 1 : return CPRES_ERROR_OUT_OF_MEMORY;
123 : }
124 :
125 17 : encoder = avifEncoderCreate();
126 17 : if (!encoder) {
127 0 : avifImageDestroy(image);
128 0 : avif_set_last_error(AVIF_RESULT_OUT_OF_MEMORY);
129 :
130 0 : return CPRES_ERROR_OUT_OF_MEMORY;
131 : }
132 :
133 17 : apply_avif_config(encoder, config);
134 :
135 17 : result = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
136 17 : if (result != AVIF_RESULT_OK) {
137 0 : avif_set_last_error(result);
138 0 : avifEncoderDestroy(encoder);
139 0 : avifImageDestroy(image);
140 :
141 0 : if (result == AVIF_RESULT_OUT_OF_MEMORY) {
142 0 : return CPRES_ERROR_OUT_OF_MEMORY;
143 : }
144 :
145 0 : return CPRES_ERROR_ENCODE_FAILED;
146 : }
147 :
148 17 : result = avifEncoderFinish(encoder, output);
149 :
150 17 : if (result != AVIF_RESULT_OK) {
151 0 : avif_set_last_error(result);
152 0 : avifEncoderDestroy(encoder);
153 0 : avifImageDestroy(image);
154 :
155 0 : if (result == AVIF_RESULT_OUT_OF_MEMORY) {
156 0 : return CPRES_ERROR_OUT_OF_MEMORY;
157 : }
158 :
159 0 : return CPRES_ERROR_ENCODE_FAILED;
160 : }
161 :
162 17 : avif_set_last_error(AVIF_RESULT_OK);
163 17 : avifEncoderDestroy(encoder);
164 17 : avifImageDestroy(image);
165 :
166 17 : return CPRES_OK;
167 : }
168 :
169 1 : extern int avif_get_last_error(void)
170 : {
171 1 : return g_avif_last_error;
172 : }
173 :
174 20 : extern void avif_set_last_error(int error_code)
175 : {
176 20 : g_avif_last_error = error_code;
177 20 : }
178 :
179 23 : extern cpres_error_t avif_encode_rgba_to_memory(uint8_t *rgba_data, uint32_t width, uint32_t height,
180 : uint8_t **avif_data, size_t *avif_size,
181 : const cpres_config_t *config)
182 : {
183 23 : avifRWData encoded = AVIF_DATA_EMPTY;
184 : cpres_error_t err;
185 :
186 23 : if (!rgba_data || !avif_data || !avif_size || !config) {
187 4 : return CPRES_ERROR_INVALID_PARAMETER;
188 : }
189 :
190 19 : *avif_data = NULL;
191 19 : *avif_size = 0;
192 :
193 19 : if (!isfinite(config->avif_quality)) {
194 1 : return CPRES_ERROR_INVALID_PARAMETER;
195 : }
196 :
197 18 : err = encode_avif_common(rgba_data, width, height, &encoded, config);
198 18 : if (err != CPRES_OK) {
199 1 : if (encoded.data) {
200 0 : avifRWDataFree(&encoded);
201 : }
202 :
203 1 : return err;
204 : }
205 :
206 17 : *avif_size = encoded.size;
207 17 : *avif_data = (uint8_t *)malloc(encoded.size);
208 17 : if (!*avif_data) {
209 0 : avifRWDataFree(&encoded);
210 :
211 0 : return CPRES_ERROR_OUT_OF_MEMORY;
212 : }
213 :
214 17 : memcpy(*avif_data, encoded.data, encoded.size);
215 17 : avifRWDataFree(&encoded);
216 :
217 17 : return CPRES_OK;
218 : }
|