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 */
37static 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 */
53static 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
64int 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