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 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 <stdbool.h>
14 : #include <stdint.h>
15 : #include <stdio.h>
16 : #include <stdlib.h>
17 : #include <string.h>
18 :
19 : #include <avif/avif.h>
20 :
21 : #include <colopresso.h>
22 :
23 : #include "internal/avif.h"
24 : #include "internal/log.h"
25 :
26 : static int g_avif_last_error = 0;
27 :
28 1 : int cpres_avif_get_last_error(void) { return g_avif_last_error; }
29 :
30 20 : void cpres_avif_set_last_error(int error_code) { g_avif_last_error = error_code; }
31 :
32 17 : static void apply_avif_config(avifEncoder *encoder, const cpres_config_t *config) {
33 : float quality;
34 : int alpha_quality, threads, speed;
35 :
36 17 : if (!encoder || !config) {
37 0 : return;
38 : }
39 :
40 17 : quality = config->avif_quality;
41 17 : alpha_quality = config->avif_alpha_quality;
42 17 : threads = config->avif_threads;
43 :
44 17 : if (quality < 0) {
45 1 : quality = 0;
46 16 : } else if (quality > 100) {
47 1 : quality = 100;
48 : }
49 17 : if (alpha_quality < 0) {
50 1 : alpha_quality = 0;
51 16 : } else if (alpha_quality > 100) {
52 1 : alpha_quality = 100;
53 : }
54 :
55 17 : encoder->quality = quality;
56 17 : encoder->qualityAlpha = alpha_quality;
57 :
58 17 : if (threads > 0) {
59 17 : encoder->maxThreads = threads;
60 : }
61 :
62 17 : speed = config->avif_speed;
63 17 : if (speed < 0 || speed > 10) {
64 2 : if (speed < 0) {
65 1 : speed = 0;
66 1 : } else if (speed > 10) {
67 1 : speed = 10;
68 : }
69 : }
70 17 : encoder->speed = speed;
71 :
72 17 : if (config->avif_lossless) {
73 3 : encoder->quality = AVIF_QUALITY_LOSSLESS;
74 3 : encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
75 : }
76 : }
77 :
78 18 : static avifImage *create_avif_image_from_rgba(uint8_t *rgba_data, uint32_t width, uint32_t height) {
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 0 : return NULL;
101 : }
102 :
103 17 : return image;
104 : }
105 :
106 18 : static cpres_error_t encode_avif_common(uint8_t *rgba_data, uint32_t width, uint32_t height, avifRWData *output, const cpres_config_t *config) {
107 18 : avifImage *image = NULL;
108 18 : avifEncoder *encoder = NULL;
109 : avifResult result;
110 :
111 18 : if (!rgba_data || !output || !config) {
112 0 : return CPRES_ERROR_INVALID_PARAMETER;
113 : }
114 :
115 18 : image = create_avif_image_from_rgba(rgba_data, width, height);
116 18 : if (!image) {
117 1 : cpres_avif_set_last_error(AVIF_RESULT_OUT_OF_MEMORY);
118 1 : return CPRES_ERROR_OUT_OF_MEMORY;
119 : }
120 :
121 17 : encoder = avifEncoderCreate();
122 17 : if (!encoder) {
123 0 : avifImageDestroy(image);
124 0 : cpres_avif_set_last_error(AVIF_RESULT_OUT_OF_MEMORY);
125 0 : return CPRES_ERROR_OUT_OF_MEMORY;
126 : }
127 :
128 17 : apply_avif_config(encoder, config);
129 :
130 17 : result = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
131 17 : if (result != AVIF_RESULT_OK) {
132 0 : cpres_avif_set_last_error(result);
133 0 : avifEncoderDestroy(encoder);
134 0 : avifImageDestroy(image);
135 0 : if (result == AVIF_RESULT_OUT_OF_MEMORY) {
136 0 : return CPRES_ERROR_OUT_OF_MEMORY;
137 : }
138 0 : return CPRES_ERROR_ENCODE_FAILED;
139 : }
140 :
141 17 : result = avifEncoderFinish(encoder, output);
142 :
143 17 : if (result != AVIF_RESULT_OK) {
144 0 : cpres_avif_set_last_error(result);
145 0 : avifEncoderDestroy(encoder);
146 0 : avifImageDestroy(image);
147 0 : if (result == AVIF_RESULT_OUT_OF_MEMORY) {
148 0 : return CPRES_ERROR_OUT_OF_MEMORY;
149 : }
150 0 : return CPRES_ERROR_ENCODE_FAILED;
151 : }
152 :
153 17 : cpres_avif_set_last_error(AVIF_RESULT_OK);
154 17 : avifEncoderDestroy(encoder);
155 17 : avifImageDestroy(image);
156 17 : return CPRES_OK;
157 : }
158 :
159 22 : cpres_error_t cpres_avif_encode_rgba_to_memory(uint8_t *rgba_data, uint32_t width, uint32_t height, uint8_t **avif_data, size_t *avif_size, const cpres_config_t *config) {
160 22 : avifRWData encoded = AVIF_DATA_EMPTY;
161 : cpres_error_t err;
162 :
163 22 : if (!rgba_data || !avif_data || !avif_size || !config) {
164 4 : return CPRES_ERROR_INVALID_PARAMETER;
165 : }
166 :
167 18 : *avif_data = NULL;
168 18 : *avif_size = 0;
169 :
170 18 : err = encode_avif_common(rgba_data, width, height, &encoded, config);
171 18 : if (err != CPRES_OK) {
172 1 : if (encoded.data) {
173 0 : avifRWDataFree(&encoded);
174 : }
175 1 : return err;
176 : }
177 :
178 17 : *avif_size = encoded.size;
179 17 : *avif_data = (uint8_t *)malloc(encoded.size);
180 17 : if (!*avif_data) {
181 0 : avifRWDataFree(&encoded);
182 0 : return CPRES_ERROR_OUT_OF_MEMORY;
183 : }
184 17 : memcpy(*avif_data, encoded.data, encoded.size);
185 17 : avifRWDataFree(&encoded);
186 :
187 17 : return CPRES_OK;
188 : }
|