1 // SPDX-License-Identifier: Apache-2.0
2 // ----------------------------------------------------------------------------
3 // Copyright 2021 Arm Limited
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 // use this file except in compliance with the License. You may obtain a copy
7 // of the License at:
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 // License for the specific language governing permissions and limitations
15 // under the License.
16 // ----------------------------------------------------------------------------
17
18 // This is a utility tool to test blend modes.
19
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23
24 #include "astcenc_mathlib.h"
25
26 #define STB_IMAGE_IMPLEMENTATION
27 #include "stb_image.h"
28
29 #define STB_IMAGE_WRITE_IMPLEMENTATION
30 #include "stb_image_write.h"
31
32 /**
33 * @brief Linearize an sRGB value.
34 *
35 * @return The linearized value.
36 */
srgb_to_linear(float a)37 static float srgb_to_linear(
38 float a
39 ) {
40 if (a <= 0.04045f)
41 {
42 return a * (1.0f / 12.92f);
43 }
44
45 return powf((a + 0.055f) * (1.0f / 1.055f), 2.4f);
46 }
47
48 /**
49 * @brief sRGB gamma-encode a linear value.
50 *
51 * @return The gamma encoded value.
52 */
linear_to_srgb(float a)53 static float linear_to_srgb(
54 float a
55 ) {
56 if (a <= 0.0031308f)
57 {
58 return a * 12.92f;
59 }
60
61 return 1.055f * powf(a, 1.0f / 2.4f) - 0.055f;
62 }
63
main(int argc,char ** argv)64 int main(int argc, char **argv)
65 {
66 // Parse command line
67 if (argc != 6)
68 {
69 printf("Usage: astc_blend_test <source> <dest> <format> <blend_mode> <filter>\n");
70 exit(1);
71 }
72
73 const char* src_file = argv[1];
74 const char* dst_file = argv[2];
75
76 bool use_linear = false;
77 if (!strcmp(argv[3], "linear"))
78 {
79 use_linear = true;
80 }
81 else if (!strcmp(argv[3], "srgb"))
82 {
83 use_linear = false;
84 }
85 else
86 {
87 printf("<format> must be either 'linear' or 'srgb'\n");
88 exit(1);
89 }
90
91 bool use_post_blend = false;
92 if (!strcmp(argv[4], "post"))
93 {
94 use_post_blend = true;
95 }
96 else if (!strcmp(argv[4], "pre"))
97 {
98 use_post_blend = false;
99 }
100 else
101 {
102 printf("<blend_mode> must be either 'post' or 'pre'\n");
103 exit(1);
104 }
105
106 bool use_filter = false;
107 if (!strcmp(argv[5], "on"))
108 {
109 use_filter = true;
110 }
111 else if (!strcmp(argv[5], "off"))
112 {
113 use_filter == false;
114 }
115 else
116 {
117 printf("<filter> must be either 'on' or 'off'\n");
118 exit(1);
119 }
120
121 // Load the input image
122 int dim_x;
123 int dim_y;
124 const uint8_t* data_in = stbi_load(src_file, &dim_x, &dim_y, nullptr, 4);
125 if (!data_in)
126 {
127 printf("ERROR: Failed to load input image.\n");
128 exit(1);
129 }
130
131 // Allocate the output image
132 uint8_t* data_out = (uint8_t*)malloc(4 * dim_y * dim_x);
133 if (!data_out)
134 {
135 printf("ERROR: Failed to allocate output image.\n");
136 exit(1);
137 }
138
139 // For each pixel apply RGBM encoding
140 if (!use_filter)
141 {
142 for (int y = 0; y < dim_y; y++)
143 {
144 const uint8_t* row_in = data_in + (4 * dim_x * y);
145 uint8_t* row_out = data_out + (4 * dim_x * y);
146
147 for (int x = 0; x < dim_x; x++)
148 {
149 const uint8_t* pixel_in = row_in + 4 * x;
150 uint8_t* pixel_out = row_out + 4 * x;
151
152 float r_src = static_cast<float>(pixel_in[0]) / 255.0f;
153 float g_src = static_cast<float>(pixel_in[1]) / 255.0f;
154 float b_src = static_cast<float>(pixel_in[2]) / 255.0f;
155 float a_src = static_cast<float>(pixel_in[3]) / 255.0f;
156
157 if (use_linear == false)
158 {
159 r_src = srgb_to_linear(r_src);
160 g_src = srgb_to_linear(g_src);
161 b_src = srgb_to_linear(b_src);
162 }
163
164 float r_dst = 0.8f;
165 float g_dst = 1.0f;
166 float b_dst = 0.8f;
167
168 float r_out;
169 float g_out;
170 float b_out;
171 float a_out;
172
173 // Post-multiply blending
174 if (use_post_blend)
175 {
176 r_out = (r_dst * (1.0f - a_src)) + (r_src * a_src);
177 g_out = (g_dst * (1.0f - a_src)) + (g_src * a_src);
178 b_out = (b_dst * (1.0f - a_src)) + (b_src * a_src);
179 a_out = 1.0f;
180 }
181 // Pre-multiply blending
182 else
183 {
184 r_out = (r_dst * (1.0f - a_src)) + (r_src * 1.0f);
185 g_out = (g_dst * (1.0f - a_src)) + (g_src * 1.0f);
186 b_out = (b_dst * (1.0f - a_src)) + (b_src * 1.0f);
187 a_out = 1.0f;
188 }
189
190 // Clamp color between 0 and 1.0f
191 r_out = astc::min(r_out, 1.0f);
192 g_out = astc::min(g_out, 1.0f);
193 b_out = astc::min(b_out, 1.0f);
194
195 if (use_linear == false)
196 {
197 r_out = linear_to_srgb(r_out);
198 g_out = linear_to_srgb(g_out);
199 b_out = linear_to_srgb(b_out);
200 }
201
202 pixel_out[0] = (uint8_t)(r_out * 255.0f);
203 pixel_out[1] = (uint8_t)(g_out * 255.0f);
204 pixel_out[2] = (uint8_t)(b_out * 255.0f);
205 pixel_out[3] = (uint8_t)(a_out * 255.0f);
206 }
207 }
208 }
209 else
210 {
211 for (int y = 0; y < dim_y - 1; y++)
212 {
213 const uint8_t* row_in_0 = data_in + (4 * dim_x * y);
214 const uint8_t* row_in_1 = data_in + (4 * dim_x * (y + 1));
215
216 uint8_t* row_out = data_out + (4 * (dim_x - 1) * y);
217
218 for (int x = 0; x < dim_x - 1; x++)
219 {
220 const uint8_t* pixel_in_00 = row_in_0 + 4 * x;
221 const uint8_t* pixel_in_01 = row_in_0 + 4 * (x + 1);
222 const uint8_t* pixel_in_10 = row_in_1 + 4 * x;
223 const uint8_t* pixel_in_11 = row_in_1 + 4 * (x + 1);
224
225 uint8_t* pixel_out = row_out + 4 * x;
226
227 // Bilinear filter with a half-pixel offset
228 float r_src = static_cast<float>(pixel_in_00[0] + pixel_in_01[0] + pixel_in_10[0] + pixel_in_11[0]) / (255.0f * 4.0f);
229 float g_src = static_cast<float>(pixel_in_00[1] + pixel_in_01[1] + pixel_in_10[1] + pixel_in_11[1]) / (255.0f * 4.0f);
230 float b_src = static_cast<float>(pixel_in_00[2] + pixel_in_01[2] + pixel_in_10[2] + pixel_in_11[2]) / (255.0f * 4.0f);
231 float a_src = static_cast<float>(pixel_in_00[3] + pixel_in_01[3] + pixel_in_10[3] + pixel_in_11[3]) / (255.0f * 4.0f);
232
233 if (use_linear == false)
234 {
235 r_src = srgb_to_linear(r_src);
236 g_src = srgb_to_linear(g_src);
237 b_src = srgb_to_linear(b_src);
238 }
239
240 float r_dst = 0.8f;
241 float g_dst = 1.0f;
242 float b_dst = 0.8f;
243
244 float r_out;
245 float g_out;
246 float b_out;
247 float a_out;
248
249 // Post-multiply blending
250 if (use_post_blend)
251 {
252 r_out = (r_dst * (1.0f - a_src)) + (r_src * a_src);
253 g_out = (g_dst * (1.0f - a_src)) + (g_src * a_src);
254 b_out = (b_dst * (1.0f - a_src)) + (b_src * a_src);
255 a_out = 1.0f;
256 }
257 // Pre-multiply blending
258 else
259 {
260 r_out = (r_dst * (1.0f - a_src)) + (r_src * 1.0f);
261 g_out = (g_dst * (1.0f - a_src)) + (g_src * 1.0f);
262 b_out = (b_dst * (1.0f - a_src)) + (b_src * 1.0f);
263 a_out = 1.0f;
264 }
265
266 // Clamp color between 0 and 1.0f
267 r_out = astc::min(r_out, 1.0f);
268 g_out = astc::min(g_out, 1.0f);
269 b_out = astc::min(b_out, 1.0f);
270
271 if (use_linear == false)
272 {
273 r_out = linear_to_srgb(r_out);
274 g_out = linear_to_srgb(g_out);
275 b_out = linear_to_srgb(b_out);
276 }
277
278 pixel_out[0] = (uint8_t)(r_out * 255.0f);
279 pixel_out[1] = (uint8_t)(g_out * 255.0f);
280 pixel_out[2] = (uint8_t)(b_out * 255.0f);
281 pixel_out[3] = (uint8_t)(a_out * 255.0f);
282 }
283 }
284 }
285
286 // Write out the result
287 if (!use_filter)
288 {
289 stbi_write_png(dst_file, dim_x, dim_y, 4, data_out, 4 * dim_x);
290 }
291 else
292 {
293 stbi_write_png(dst_file, dim_x - 1, dim_y - 1, 4, data_out, 4 * (dim_x - 1));
294 }
295
296
297 return 0;
298 }
299