Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/src/reformat.c
Line
Count
Source
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avif/internal.h"
5
6
#include <assert.h>
7
#include <stdint.h>
8
#include <string.h>
9
10
#if defined(_WIN32)
11
#include <process.h>
12
#include <windows.h>
13
#else
14
#include <pthread.h>
15
#endif
16
17
static void * avifMemset16(void * dest, int val, size_t count)
18
0
{
19
0
    uint16_t * dest16 = (uint16_t *)dest;
20
0
    for (size_t i = 0; i < count; i++)
21
0
        *dest16++ = (uint16_t)val;
22
0
    return dest;
23
0
}
24
25
struct YUVBlock
26
{
27
    float y;
28
    float u;
29
    float v;
30
};
31
32
avifBool avifGetRGBColorSpaceInfo(const avifRGBImage * rgb, avifRGBColorSpaceInfo * info)
33
2.37k
{
34
2.37k
    AVIF_CHECK(rgb->depth == 8 || rgb->depth == 10 || rgb->depth == 12 || rgb->depth == 16);
35
2.37k
    if (rgb->isFloat) {
36
0
        AVIF_CHECK(rgb->depth == 16);
37
0
    }
38
2.37k
    if (rgb->format == AVIF_RGB_FORMAT_RGB_565) {
39
0
        AVIF_CHECK(rgb->depth == 8);
40
0
    }
41
    // Cast to silence "comparison of unsigned expression is always true" warning.
42
2.37k
    AVIF_CHECK((int)rgb->format >= AVIF_RGB_FORMAT_RGB && rgb->format < AVIF_RGB_FORMAT_COUNT);
43
44
2.37k
    info->channelBytes = (rgb->depth > 8) ? 2 : 1;
45
2.37k
    info->pixelBytes = avifRGBImagePixelSize(rgb);
46
47
2.37k
    info->offsetBytesR = 0;
48
2.37k
    info->offsetBytesG = 0;
49
2.37k
    info->offsetBytesB = 0;
50
2.37k
    info->offsetBytesA = 0;
51
2.37k
    info->offsetBytesGray = 0;
52
53
2.37k
    switch (rgb->format) {
54
0
        case AVIF_RGB_FORMAT_RGB:
55
0
            info->offsetBytesR = info->channelBytes * 0;
56
0
            info->offsetBytesG = info->channelBytes * 1;
57
0
            info->offsetBytesB = info->channelBytes * 2;
58
0
            break;
59
682
        case AVIF_RGB_FORMAT_RGBA:
60
682
            info->offsetBytesR = info->channelBytes * 0;
61
682
            info->offsetBytesG = info->channelBytes * 1;
62
682
            info->offsetBytesB = info->channelBytes * 2;
63
682
            info->offsetBytesA = info->channelBytes * 3;
64
682
            break;
65
0
        case AVIF_RGB_FORMAT_ARGB:
66
0
            info->offsetBytesA = info->channelBytes * 0;
67
0
            info->offsetBytesR = info->channelBytes * 1;
68
0
            info->offsetBytesG = info->channelBytes * 2;
69
0
            info->offsetBytesB = info->channelBytes * 3;
70
0
            break;
71
0
        case AVIF_RGB_FORMAT_BGR:
72
0
            info->offsetBytesB = info->channelBytes * 0;
73
0
            info->offsetBytesG = info->channelBytes * 1;
74
0
            info->offsetBytesR = info->channelBytes * 2;
75
0
            break;
76
1.68k
        case AVIF_RGB_FORMAT_BGRA:
77
1.68k
            info->offsetBytesB = info->channelBytes * 0;
78
1.68k
            info->offsetBytesG = info->channelBytes * 1;
79
1.68k
            info->offsetBytesR = info->channelBytes * 2;
80
1.68k
            info->offsetBytesA = info->channelBytes * 3;
81
1.68k
            break;
82
0
        case AVIF_RGB_FORMAT_ABGR:
83
0
            info->offsetBytesA = info->channelBytes * 0;
84
0
            info->offsetBytesB = info->channelBytes * 1;
85
0
            info->offsetBytesG = info->channelBytes * 2;
86
0
            info->offsetBytesR = info->channelBytes * 3;
87
0
            break;
88
0
        case AVIF_RGB_FORMAT_RGB_565:
89
            // Since RGB_565 consists of two bytes per RGB pixel, we simply use
90
            // the pointer to the red channel to populate the entire pixel value
91
            // as a uint16_t. As a result only offsetBytesR is used and the
92
            // other offsets are unused.
93
0
            info->offsetBytesR = 0;
94
0
            info->offsetBytesG = 0;
95
0
            info->offsetBytesB = 0;
96
0
            break;
97
0
        case AVIF_RGB_FORMAT_GRAY:
98
0
            info->offsetBytesGray = info->channelBytes * 0;
99
0
            break;
100
0
        case AVIF_RGB_FORMAT_GRAYA:
101
0
            info->offsetBytesGray = info->channelBytes * 0;
102
0
            info->offsetBytesA = info->channelBytes * 1;
103
0
            break;
104
0
        case AVIF_RGB_FORMAT_AGRAY:
105
0
            info->offsetBytesA = info->channelBytes * 0;
106
0
            info->offsetBytesGray = info->channelBytes * 1;
107
0
            break;
108
109
0
        case AVIF_RGB_FORMAT_COUNT:
110
0
            return AVIF_FALSE;
111
2.37k
    }
112
113
2.37k
    info->maxChannel = (1 << rgb->depth) - 1;
114
2.37k
    info->maxChannelF = (float)info->maxChannel;
115
116
2.37k
    return AVIF_TRUE;
117
2.37k
}
118
119
avifBool avifGetYUVColorSpaceInfo(const avifImage * image, avifYUVColorSpaceInfo * info)
120
2.37k
{
121
2.37k
    AVIF_CHECK(image->depth == 8 || image->depth == 10 || image->depth == 12 || image->depth == 16);
122
2.37k
    AVIF_CHECK(image->yuvFormat >= AVIF_PIXEL_FORMAT_YUV444 && image->yuvFormat < AVIF_PIXEL_FORMAT_COUNT);
123
2.37k
    AVIF_CHECK(image->yuvRange == AVIF_RANGE_LIMITED || image->yuvRange == AVIF_RANGE_FULL);
124
125
    // These matrix coefficients values are currently unsupported. Revise this list as more support is added.
126
    //
127
    // YCgCo performs limited-full range adjustment on R,G,B but the current implementation performs range adjustment
128
    // on Y,U,V. So YCgCo with limited range is unsupported.
129
2.37k
    if ((image->matrixCoefficients == 3 /* CICP reserved */) ||
130
2.36k
        ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO || image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE ||
131
2.23k
          image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO) &&
132
135
         (image->yuvRange == AVIF_RANGE_LIMITED)) ||
133
2.36k
        (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT2020_CL) ||
134
2.36k
        (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_SMPTE2085) ||
135
2.35k
        (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL) ||
136
2.35k
        (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_ICTCP) || (image->matrixCoefficients >= AVIF_MATRIX_COEFFICIENTS_LAST)) {
137
147
        return AVIF_FALSE;
138
147
    }
139
140
    // Removing 400 here would break backward behavior but would respect the spec.
141
2.22k
    if ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) && (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444) &&
142
31
        (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400)) {
143
0
        return AVIF_FALSE;
144
0
    }
145
2.22k
    avifGetPixelFormatInfo(image->yuvFormat, &info->formatInfo);
146
2.22k
    avifCalcYUVCoefficients(image, &info->kr, &info->kg, &info->kb);
147
148
2.22k
    info->channelBytes = (image->depth > 8) ? 2 : 1;
149
150
2.22k
    info->depth = image->depth;
151
2.22k
    info->range = image->yuvRange;
152
2.22k
    info->maxChannel = (1 << image->depth) - 1;
153
2.22k
    info->biasY = (info->range == AVIF_RANGE_LIMITED) ? (float)(16 << (info->depth - 8)) : 0.0f;
154
2.22k
    info->biasUV = (float)(1 << (info->depth - 1));
155
2.22k
    info->rangeY = (float)((info->range == AVIF_RANGE_LIMITED) ? (219 << (info->depth - 8)) : info->maxChannel);
156
2.22k
    info->rangeUV = (float)((info->range == AVIF_RANGE_LIMITED) ? (224 << (info->depth - 8)) : info->maxChannel);
157
158
2.22k
    return AVIF_TRUE;
159
2.22k
}
160
161
static avifBool avifPrepareReformatState(const avifImage * image, const avifRGBImage * rgb, avifReformatState * state)
162
2.38k
{
163
2.38k
    const avifBool useYCgCoRe = (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE);
164
2.38k
    const avifBool useYCgCoRo = (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO);
165
2.38k
    if (useYCgCoRe || useYCgCoRo) {
166
52
        const int bitOffset = (useYCgCoRe) ? 2 : 1;
167
52
        if (image->depth - bitOffset != rgb->depth) {
168
12
            return AVIF_FALSE;
169
12
        }
170
52
    }
171
172
2.37k
    AVIF_CHECK(avifGetRGBColorSpaceInfo(rgb, &state->rgb));
173
2.37k
    AVIF_CHECK(avifGetYUVColorSpaceInfo(image, &state->yuv));
174
175
2.22k
    state->yuv.mode = AVIF_REFORMAT_MODE_YUV_COEFFICIENTS;
176
177
2.22k
    if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) {
178
950
        state->yuv.mode = AVIF_REFORMAT_MODE_IDENTITY;
179
1.27k
    } else if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO) {
180
95
        state->yuv.mode = AVIF_REFORMAT_MODE_YCGCO;
181
1.17k
    } else if (useYCgCoRe) {
182
35
        state->yuv.mode = AVIF_REFORMAT_MODE_YCGCO_RE;
183
1.14k
    } else if (useYCgCoRo) {
184
0
        state->yuv.mode = AVIF_REFORMAT_MODE_YCGCO_RO;
185
0
    }
186
187
2.22k
    if (state->yuv.mode != AVIF_REFORMAT_MODE_YUV_COEFFICIENTS) {
188
1.08k
        state->yuv.kr = 0.0f;
189
1.08k
        state->yuv.kg = 0.0f;
190
1.08k
        state->yuv.kb = 0.0f;
191
1.08k
    }
192
193
2.22k
    return AVIF_TRUE;
194
2.37k
}
195
196
// Formulas 20-31 from https://www.itu.int/rec/T-REC-H.273-201612-S
197
static int avifYUVColorSpaceInfoYToUNorm(avifYUVColorSpaceInfo * info, float v)
198
0
{
199
0
    int unorm = (int)avifRoundf(v * info->rangeY + info->biasY);
200
0
    return AVIF_CLAMP(unorm, 0, info->maxChannel);
201
0
}
202
203
static int avifYUVColorSpaceInfoUVToUNorm(avifYUVColorSpaceInfo * info, float v)
204
0
{
205
0
    int unorm;
206
207
    // YCgCo performs limited-full range adjustment on R,G,B but the current implementation performs range adjustment
208
    // on Y,U,V. So YCgCo with limited range is unsupported.
209
0
    assert((info->mode != AVIF_REFORMAT_MODE_YCGCO && info->mode != AVIF_REFORMAT_MODE_YCGCO_RE && info->mode != AVIF_REFORMAT_MODE_YCGCO_RO) ||
210
0
           (info->range == AVIF_RANGE_FULL));
211
212
0
    if (info->mode == AVIF_REFORMAT_MODE_IDENTITY) {
213
0
        unorm = (int)avifRoundf(v * info->rangeY + info->biasY);
214
0
    } else {
215
0
        unorm = (int)avifRoundf(v * info->rangeUV + info->biasUV);
216
0
    }
217
218
0
    return AVIF_CLAMP(unorm, 0, info->maxChannel);
219
0
}
220
221
avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb)
222
0
{
223
0
    if (!rgb->pixels || rgb->format == AVIF_RGB_FORMAT_RGB_565) {
224
0
        return AVIF_RESULT_REFORMAT_FAILED;
225
0
    }
226
227
0
    avifReformatState state;
228
0
    if (!avifPrepareReformatState(image, rgb, &state)) {
229
0
        return AVIF_RESULT_REFORMAT_FAILED;
230
0
    }
231
232
0
    if (rgb->isFloat) {
233
0
        return AVIF_RESULT_NOT_IMPLEMENTED;
234
0
    }
235
236
0
    const avifBool hasAlpha = avifRGBFormatHasAlpha(rgb->format) && !rgb->ignoreAlpha;
237
0
    avifResult allocationResult = avifImageAllocatePlanes(image, hasAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV);
238
0
    if (allocationResult != AVIF_RESULT_OK) {
239
0
        return allocationResult;
240
0
    }
241
242
0
    avifAlphaMultiplyMode alphaMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP;
243
0
    if (hasAlpha) {
244
0
        if (!rgb->alphaPremultiplied && image->alphaPremultiplied) {
245
0
            alphaMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY;
246
0
        } else if (rgb->alphaPremultiplied && !image->alphaPremultiplied) {
247
0
            alphaMode = AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY;
248
0
        }
249
0
    }
250
251
0
    const avifBool isGray = avifRGBFormatIsGray(rgb->format);
252
0
    avifBool converted = AVIF_FALSE;
253
254
    // Try converting with libsharpyuv.
255
0
    if (!isGray) {
256
0
        if ((rgb->chromaDownsampling == AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV) && (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) {
257
0
            const avifResult libSharpYUVResult = avifImageRGBToYUVLibSharpYUV(image, rgb, &state);
258
0
            if (libSharpYUVResult != AVIF_RESULT_OK) {
259
                // Return the error if sharpyuv was requested but failed for any reason, including libsharpyuv not being available.
260
0
                return libSharpYUVResult;
261
0
            }
262
0
            converted = AVIF_TRUE;
263
0
        }
264
265
0
        if (!converted && !rgb->avoidLibYUV && (alphaMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP)) {
266
0
            avifResult libyuvResult = avifImageRGBToYUVLibYUV(image, rgb);
267
0
            if (libyuvResult == AVIF_RESULT_OK) {
268
0
                converted = AVIF_TRUE;
269
0
            } else if (libyuvResult != AVIF_RESULT_NOT_IMPLEMENTED) {
270
0
                return libyuvResult;
271
0
            }
272
0
        }
273
0
    }
274
275
0
    if (!converted && !isGray) {
276
0
        const float kr = state.yuv.kr;
277
0
        const float kg = state.yuv.kg;
278
0
        const float kb = state.yuv.kb;
279
280
0
        struct YUVBlock yuvBlock[2][2];
281
0
        float rgbPixel[3];
282
0
        const uint32_t rgbPixelBytes = state.rgb.pixelBytes;
283
0
        const uint32_t offsetBytesR = state.rgb.offsetBytesR;
284
0
        const uint32_t offsetBytesG = state.rgb.offsetBytesG;
285
0
        const uint32_t offsetBytesB = state.rgb.offsetBytesB;
286
0
        const uint32_t offsetBytesA = state.rgb.offsetBytesA;
287
0
        const size_t rgbRowBytes = rgb->rowBytes;
288
0
        const float rgbMaxChannelF = state.rgb.maxChannelF;
289
0
        uint8_t * yPlane = image->yuvPlanes[AVIF_CHAN_Y];
290
0
        uint8_t * uPlane = image->yuvPlanes[AVIF_CHAN_U];
291
0
        uint8_t * vPlane = image->yuvPlanes[AVIF_CHAN_V];
292
0
        const size_t yRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
293
0
        const size_t uRowBytes = image->yuvRowBytes[AVIF_CHAN_U];
294
0
        const size_t vRowBytes = image->yuvRowBytes[AVIF_CHAN_V];
295
0
        for (size_t outerJ = 0; outerJ < image->height; outerJ += 2) {
296
0
            for (size_t outerI = 0; outerI < image->width; outerI += 2) {
297
0
                uint32_t blockW = 2, blockH = 2;
298
0
                if ((outerI + 1) >= image->width) {
299
0
                    blockW = 1;
300
0
                }
301
0
                if ((outerJ + 1) >= image->height) {
302
0
                    blockH = 1;
303
0
                }
304
305
                // Convert an entire 2x2 block to YUV, and populate any fully sampled channels as we go
306
0
                for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
307
0
                    for (uint32_t bI = 0; bI < blockW; ++bI) {
308
0
                        const size_t i = outerI + bI;
309
0
                        const size_t j = outerJ + bJ;
310
311
                        // Unpack RGB into normalized float
312
0
                        if (state.rgb.channelBytes > 1) {
313
0
                            rgbPixel[0] = *((uint16_t *)(&rgb->pixels[offsetBytesR + (i * rgbPixelBytes) + (j * rgbRowBytes)])) /
314
0
                                          rgbMaxChannelF;
315
0
                            rgbPixel[1] = *((uint16_t *)(&rgb->pixels[offsetBytesG + (i * rgbPixelBytes) + (j * rgbRowBytes)])) /
316
0
                                          rgbMaxChannelF;
317
0
                            rgbPixel[2] = *((uint16_t *)(&rgb->pixels[offsetBytesB + (i * rgbPixelBytes) + (j * rgbRowBytes)])) /
318
0
                                          rgbMaxChannelF;
319
0
                        } else {
320
0
                            rgbPixel[0] = rgb->pixels[offsetBytesR + (i * rgbPixelBytes) + (j * rgbRowBytes)] / rgbMaxChannelF;
321
0
                            rgbPixel[1] = rgb->pixels[offsetBytesG + (i * rgbPixelBytes) + (j * rgbRowBytes)] / rgbMaxChannelF;
322
0
                            rgbPixel[2] = rgb->pixels[offsetBytesB + (i * rgbPixelBytes) + (j * rgbRowBytes)] / rgbMaxChannelF;
323
0
                        }
324
325
0
                        if (alphaMode != AVIF_ALPHA_MULTIPLY_MODE_NO_OP) {
326
0
                            float a;
327
0
                            if (state.rgb.channelBytes > 1) {
328
0
                                a = *((uint16_t *)(&rgb->pixels[offsetBytesA + (i * rgbPixelBytes) + (j * rgbRowBytes)])) / rgbMaxChannelF;
329
0
                            } else {
330
0
                                a = rgb->pixels[offsetBytesA + (i * rgbPixelBytes) + (j * rgbRowBytes)] / rgbMaxChannelF;
331
0
                            }
332
333
0
                            if (alphaMode == AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY) {
334
0
                                if (a == 0) {
335
0
                                    rgbPixel[0] = 0;
336
0
                                    rgbPixel[1] = 0;
337
0
                                    rgbPixel[2] = 0;
338
0
                                } else if (a < 1.0f) {
339
0
                                    rgbPixel[0] *= a;
340
0
                                    rgbPixel[1] *= a;
341
0
                                    rgbPixel[2] *= a;
342
0
                                }
343
0
                            } else {
344
                                // alphaMode == AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY
345
0
                                if (a == 0) {
346
0
                                    rgbPixel[0] = 0;
347
0
                                    rgbPixel[1] = 0;
348
0
                                    rgbPixel[2] = 0;
349
0
                                } else if (a < 1.0f) {
350
0
                                    rgbPixel[0] /= a;
351
0
                                    rgbPixel[1] /= a;
352
0
                                    rgbPixel[2] /= a;
353
0
                                    rgbPixel[0] = AVIF_MIN(rgbPixel[0], 1.0f);
354
0
                                    rgbPixel[1] = AVIF_MIN(rgbPixel[1], 1.0f);
355
0
                                    rgbPixel[2] = AVIF_MIN(rgbPixel[2], 1.0f);
356
0
                                }
357
0
                            }
358
0
                        }
359
360
                        // RGB -> YUV conversion
361
0
                        if (state.yuv.mode == AVIF_REFORMAT_MODE_IDENTITY) {
362
                            // Formulas 41,42,43 from https://www.itu.int/rec/T-REC-H.273-201612-S
363
0
                            yuvBlock[bI][bJ].y = rgbPixel[1]; // G
364
0
                            yuvBlock[bI][bJ].u = rgbPixel[2]; // B
365
0
                            yuvBlock[bI][bJ].v = rgbPixel[0]; // R
366
0
                        } else if (state.yuv.mode == AVIF_REFORMAT_MODE_YCGCO) {
367
                            // Formulas 44,45,46 from https://www.itu.int/rec/T-REC-H.273-201612-S
368
0
                            yuvBlock[bI][bJ].y = 0.5f * rgbPixel[1] + 0.25f * (rgbPixel[0] + rgbPixel[2]);
369
0
                            yuvBlock[bI][bJ].u = 0.5f * rgbPixel[1] - 0.25f * (rgbPixel[0] + rgbPixel[2]);
370
0
                            yuvBlock[bI][bJ].v = 0.5f * (rgbPixel[0] - rgbPixel[2]);
371
0
                        } else if (state.yuv.mode == AVIF_REFORMAT_MODE_YCGCO_RE || state.yuv.mode == AVIF_REFORMAT_MODE_YCGCO_RO) {
372
                            // Formulas 58,59,60,61 from https://www.itu.int/rec/T-REC-H.273-202407-P
373
0
                            const int R = (int)avifRoundf(AVIF_CLAMP(rgbPixel[0] * rgbMaxChannelF, 0.0f, rgbMaxChannelF));
374
0
                            const int G = (int)avifRoundf(AVIF_CLAMP(rgbPixel[1] * rgbMaxChannelF, 0.0f, rgbMaxChannelF));
375
0
                            const int B = (int)avifRoundf(AVIF_CLAMP(rgbPixel[2] * rgbMaxChannelF, 0.0f, rgbMaxChannelF));
376
0
                            const int Co = R - B;
377
0
                            const int t = B + (Co >> 1);
378
0
                            const int Cg = G - t;
379
0
                            yuvBlock[bI][bJ].y = (t + (Cg >> 1)) / state.yuv.rangeY;
380
0
                            yuvBlock[bI][bJ].u = Cg / state.yuv.rangeUV;
381
0
                            yuvBlock[bI][bJ].v = Co / state.yuv.rangeUV;
382
0
                        } else {
383
0
                            float Y = (kr * rgbPixel[0]) + (kg * rgbPixel[1]) + (kb * rgbPixel[2]);
384
0
                            yuvBlock[bI][bJ].y = Y;
385
0
                            yuvBlock[bI][bJ].u = (rgbPixel[2] - Y) / (2 * (1 - kb));
386
0
                            yuvBlock[bI][bJ].v = (rgbPixel[0] - Y) / (2 * (1 - kr));
387
0
                        }
388
389
0
                        if (state.yuv.channelBytes > 1) {
390
0
                            uint16_t * pY = (uint16_t *)&yPlane[(i * 2) + (j * yRowBytes)];
391
0
                            *pY = (uint16_t)avifYUVColorSpaceInfoYToUNorm(&state.yuv, yuvBlock[bI][bJ].y);
392
0
                            if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) {
393
                                // YUV444, full chroma
394
0
                                uint16_t * pU = (uint16_t *)&uPlane[(i * 2) + (j * uRowBytes)];
395
0
                                *pU = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, yuvBlock[bI][bJ].u);
396
0
                                uint16_t * pV = (uint16_t *)&vPlane[(i * 2) + (j * vRowBytes)];
397
0
                                *pV = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, yuvBlock[bI][bJ].v);
398
0
                            }
399
0
                        } else {
400
0
                            yPlane[i + (j * yRowBytes)] = (uint8_t)avifYUVColorSpaceInfoYToUNorm(&state.yuv, yuvBlock[bI][bJ].y);
401
0
                            if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) {
402
                                // YUV444, full chroma
403
0
                                uPlane[i + (j * uRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, yuvBlock[bI][bJ].u);
404
0
                                vPlane[i + (j * vRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, yuvBlock[bI][bJ].v);
405
0
                            }
406
0
                        }
407
0
                    }
408
0
                }
409
410
                // Populate any subsampled channels with averages from the 2x2 block
411
0
                if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
412
                    // Do nothing on chroma planes.
413
0
                } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
414
                    // YUV420, average 4 samples (2x2)
415
416
0
                    float sumU = 0.0f;
417
0
                    float sumV = 0.0f;
418
0
                    for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
419
0
                        for (uint32_t bI = 0; bI < blockW; ++bI) {
420
0
                            sumU += yuvBlock[bI][bJ].u;
421
0
                            sumV += yuvBlock[bI][bJ].v;
422
0
                        }
423
0
                    }
424
0
                    float totalSamples = (float)(blockW * blockH);
425
0
                    float avgU = sumU / totalSamples;
426
0
                    float avgV = sumV / totalSamples;
427
428
0
                    const int chromaShiftX = 1;
429
0
                    const int chromaShiftY = 1;
430
0
                    size_t uvI = outerI >> chromaShiftX;
431
0
                    size_t uvJ = outerJ >> chromaShiftY;
432
0
                    if (state.yuv.channelBytes > 1) {
433
0
                        uint16_t * pU = (uint16_t *)&uPlane[(uvI * 2) + (uvJ * uRowBytes)];
434
0
                        *pU = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgU);
435
0
                        uint16_t * pV = (uint16_t *)&vPlane[(uvI * 2) + (uvJ * vRowBytes)];
436
0
                        *pV = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgV);
437
0
                    } else {
438
0
                        uPlane[uvI + (uvJ * uRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgU);
439
0
                        vPlane[uvI + (uvJ * vRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgV);
440
0
                    }
441
0
                } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) {
442
                    // YUV422, average 2 samples (1x2), twice
443
444
0
                    for (uint32_t bJ = 0; bJ < blockH; ++bJ) {
445
0
                        float sumU = 0.0f;
446
0
                        float sumV = 0.0f;
447
0
                        for (uint32_t bI = 0; bI < blockW; ++bI) {
448
0
                            sumU += yuvBlock[bI][bJ].u;
449
0
                            sumV += yuvBlock[bI][bJ].v;
450
0
                        }
451
0
                        float totalSamples = (float)blockW;
452
0
                        float avgU = sumU / totalSamples;
453
0
                        float avgV = sumV / totalSamples;
454
455
0
                        const int chromaShiftX = 1;
456
0
                        size_t uvI = outerI >> chromaShiftX;
457
0
                        size_t uvJ = outerJ + bJ;
458
0
                        if (state.yuv.channelBytes > 1) {
459
0
                            uint16_t * pU = (uint16_t *)&uPlane[(uvI * 2) + (uvJ * uRowBytes)];
460
0
                            *pU = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgU);
461
0
                            uint16_t * pV = (uint16_t *)&vPlane[(uvI * 2) + (uvJ * vRowBytes)];
462
0
                            *pV = (uint16_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgV);
463
0
                        } else {
464
0
                            uPlane[uvI + (uvJ * uRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgU);
465
0
                            vPlane[uvI + (uvJ * vRowBytes)] = (uint8_t)avifYUVColorSpaceInfoUVToUNorm(&state.yuv, avgV);
466
0
                        }
467
0
                    }
468
0
                }
469
0
            }
470
0
        }
471
0
    } else if (!converted && isGray) {
472
0
        const uint32_t grayPixelBytes = state.rgb.pixelBytes;
473
0
        const uint32_t offsetBytesGray = state.rgb.offsetBytesGray;
474
0
        const uint32_t offsetBytesA = state.rgb.offsetBytesA;
475
0
        const size_t grayRowBytes = rgb->rowBytes;
476
0
        const float grayMaxChannelF = state.rgb.maxChannelF;
477
0
        uint8_t * yPlane = image->yuvPlanes[AVIF_CHAN_Y];
478
0
        const size_t yRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
479
0
        for (size_t j = 0; j < image->height; ++j) {
480
0
            for (size_t i = 0; i < image->width; ++i) {
481
0
                float g;
482
0
                if (state.rgb.channelBytes > 1) {
483
0
                    g = *(uint16_t *)&rgb->pixels[offsetBytesGray + i * grayPixelBytes + (j * grayRowBytes)] / grayMaxChannelF;
484
0
                } else {
485
0
                    g = rgb->pixels[offsetBytesGray + i * grayPixelBytes + (j * grayRowBytes)] / grayMaxChannelF;
486
0
                }
487
0
                if (alphaMode != AVIF_ALPHA_MULTIPLY_MODE_NO_OP) {
488
0
                    float a;
489
0
                    if (state.rgb.channelBytes > 1) {
490
0
                        a = *((uint16_t *)(&rgb->pixels[offsetBytesA + (i * grayPixelBytes) + (j * grayRowBytes)])) / grayMaxChannelF;
491
0
                    } else {
492
0
                        a = rgb->pixels[offsetBytesA + (i * grayPixelBytes) + (j * grayRowBytes)] / grayMaxChannelF;
493
0
                    }
494
495
0
                    if (alphaMode == AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY) {
496
0
                        if (a == 0) {
497
0
                            g = 0;
498
0
                        } else if (a < 1.0f) {
499
0
                            g *= a;
500
0
                        }
501
0
                    } else {
502
                        // alphaMode == AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY
503
0
                        if (a == 0) {
504
0
                            g = 0;
505
0
                        } else if (a < 1.0f) {
506
0
                            g /= a;
507
0
                            g = AVIF_MIN(g, 1.0f);
508
0
                        }
509
0
                    }
510
0
                }
511
0
                int gInt = avifYUVColorSpaceInfoYToUNorm(&state.yuv, g);
512
0
                if (state.yuv.channelBytes > 1) {
513
0
                    uint16_t * pY = (uint16_t *)&yPlane[(i * 2) + j * yRowBytes];
514
0
                    *pY = (uint16_t)gInt;
515
0
                } else {
516
0
                    yPlane[i + (j * yRowBytes)] = (uint8_t)gInt;
517
0
                }
518
0
            }
519
0
        }
520
        // Set the chroma planes, if any, to the half value.
521
0
        avifPixelFormatInfo info;
522
0
        avifGetPixelFormatInfo(image->yuvFormat, &info);
523
0
        const uint32_t shiftedH = (uint32_t)(((uint64_t)image->height + info.chromaShiftY) >> info.chromaShiftY);
524
0
        const int half = 1 << (image->depth - 1);
525
0
        if (image->yuvPlanes[AVIF_CHAN_U]) {
526
0
            uint8_t * uPlane = image->yuvPlanes[AVIF_CHAN_U];
527
0
            const size_t uRowBytes = image->yuvRowBytes[AVIF_CHAN_U];
528
0
            if (state.yuv.channelBytes > 1) {
529
0
                avifMemset16(uPlane, half, shiftedH * uRowBytes / 2);
530
0
            } else {
531
0
                memset(uPlane, half, shiftedH * uRowBytes);
532
0
            }
533
0
        }
534
0
        if (image->yuvPlanes[AVIF_CHAN_V]) {
535
0
            uint8_t * vPlane = image->yuvPlanes[AVIF_CHAN_V];
536
0
            const size_t vRowBytes = image->yuvRowBytes[AVIF_CHAN_V];
537
0
            if (state.yuv.channelBytes > 1) {
538
0
                avifMemset16(vPlane, half, shiftedH * vRowBytes / 2);
539
0
            } else {
540
0
                memset(vPlane, half, shiftedH * vRowBytes);
541
0
            }
542
0
        }
543
0
    }
544
545
0
    if (image->alphaPlane && image->alphaRowBytes) {
546
0
        avifAlphaParams params;
547
548
0
        params.width = image->width;
549
0
        params.height = image->height;
550
0
        params.dstDepth = image->depth;
551
0
        params.dstPlane = image->alphaPlane;
552
0
        params.dstRowBytes = image->alphaRowBytes;
553
0
        params.dstOffsetBytes = 0;
554
0
        params.dstPixelBytes = state.yuv.channelBytes;
555
556
0
        if (avifRGBFormatHasAlpha(rgb->format) && !rgb->ignoreAlpha) {
557
0
            params.srcDepth = rgb->depth;
558
0
            params.srcPlane = rgb->pixels;
559
0
            params.srcRowBytes = rgb->rowBytes;
560
0
            params.srcOffsetBytes = state.rgb.offsetBytesA;
561
0
            params.srcPixelBytes = state.rgb.pixelBytes;
562
563
0
            avifReformatAlpha(&params);
564
0
        } else {
565
            // libyuv does not fill alpha when converting from RGB to YUV so
566
            // fill it regardless of the value of convertedWithLibYUV.
567
0
            avifFillAlpha(&params);
568
0
        }
569
0
    }
570
0
    return AVIF_RESULT_OK;
571
0
}
572
573
// Allocates and fills look-up tables for going from YUV limited/full unorm -> full range RGB FP32.
574
// Review this when implementing YCgCo limited range support.
575
static avifBool avifCreateYUVToRGBLookUpTables(float ** unormFloatTableY, float ** unormFloatTableUV, uint32_t depth, const avifReformatState * state)
576
6.30k
{
577
6.30k
    const size_t cpCount = (size_t)1 << depth;
578
579
6.30k
    assert(unormFloatTableY);
580
6.30k
    *unormFloatTableY = (float *)avifAlloc(cpCount * sizeof(float));
581
6.30k
    AVIF_CHECK(*unormFloatTableY);
582
7.61M
    for (uint32_t cp = 0; cp < cpCount; ++cp) {
583
7.61M
        (*unormFloatTableY)[cp] = ((float)cp - state->yuv.biasY) / state->yuv.rangeY;
584
7.61M
    }
585
586
6.30k
    if (unormFloatTableUV) {
587
5.02k
        if (state->yuv.mode == AVIF_REFORMAT_MODE_IDENTITY) {
588
            // Just reuse the luma table since the chroma values are the same.
589
384
            *unormFloatTableUV = *unormFloatTableY;
590
4.64k
        } else {
591
4.64k
            *unormFloatTableUV = (float *)avifAlloc(cpCount * sizeof(float));
592
4.64k
            if (!*unormFloatTableUV) {
593
0
                avifFree(*unormFloatTableY);
594
0
                *unormFloatTableY = NULL;
595
0
                return AVIF_FALSE;
596
0
            }
597
6.46M
            for (uint32_t cp = 0; cp < cpCount; ++cp) {
598
6.45M
                (*unormFloatTableUV)[cp] = ((float)cp - state->yuv.biasUV) / state->yuv.rangeUV;
599
6.45M
            }
600
4.64k
        }
601
5.02k
    }
602
6.30k
    return AVIF_TRUE;
603
6.30k
}
604
605
// Frees look-up tables allocated with avifCreateYUVToRGBLookUpTables().
606
static void avifFreeYUVToRGBLookUpTables(float ** unormFloatTableY, float ** unormFloatTableUV)
607
6.30k
{
608
6.30k
    if (unormFloatTableUV) {
609
5.02k
        if (*unormFloatTableUV != *unormFloatTableY) {
610
4.64k
            avifFree(*unormFloatTableUV);
611
4.64k
        }
612
5.02k
        *unormFloatTableUV = NULL;
613
5.02k
    }
614
615
6.30k
    avifFree(*unormFloatTableY);
616
6.30k
    *unormFloatTableY = NULL;
617
6.30k
}
618
619
0
#define RGB565(R, G, B) ((uint16_t)(((B) >> 3) | (((G) >> 2) << 5) | (((R) >> 3) << 11)))
620
621
static void avifStoreRGB8Pixel(avifRGBFormat format, uint8_t R, uint8_t G, uint8_t B, uint8_t * ptrR, uint8_t * ptrG, uint8_t * ptrB)
622
819M
{
623
819M
    if (format == AVIF_RGB_FORMAT_RGB_565) {
624
        // References for RGB565 color conversion:
625
        // * https://docs.microsoft.com/en-us/windows/win32/directshow/working-with-16-bit-rgb
626
        // * https://chromium.googlesource.com/libyuv/libyuv/+/9892d70c965678381d2a70a1c9002d1cf136ee78/source/row_common.cc#2362
627
0
        *(uint16_t *)ptrR = RGB565(R, G, B);
628
0
        return;
629
0
    }
630
819M
    *ptrR = R;
631
819M
    *ptrG = G;
632
819M
    *ptrB = B;
633
819M
}
634
635
static void avifGetRGB565(const uint8_t * ptrR, uint8_t * R, uint8_t * G, uint8_t * B)
636
0
{
637
    // References for RGB565 color conversion:
638
    // * https://docs.microsoft.com/en-us/windows/win32/directshow/working-with-16-bit-rgb
639
    // * https://chromium.googlesource.com/libyuv/libyuv/+/331c361581896292fb46c8c6905e41262b7ca95f/source/row_common.cc#185
640
0
    const uint16_t rgb656 = ((const uint16_t *)ptrR)[0];
641
0
    const uint16_t r5 = (rgb656 & 0xF800) >> 11;
642
0
    const uint16_t g6 = (rgb656 & 0x07E0) >> 5;
643
0
    const uint16_t b5 = (rgb656 & 0x001F);
644
0
    *R = (uint8_t)((r5 << 3) | (r5 >> 2));
645
0
    *G = (uint8_t)((g6 << 2) | (g6 >> 4));
646
0
    *B = (uint8_t)((b5 << 3) | (b5 >> 2));
647
0
}
648
649
// Note: This function handles alpha (un)multiply.
650
static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image,
651
                                              avifRGBImage * rgb,
652
                                              const avifReformatState * state,
653
                                              avifAlphaMultiplyMode alphaMultiplyMode)
654
2.13k
{
655
    // Aliases for some state
656
2.13k
    const float kr = state->yuv.kr;
657
2.13k
    const float kg = state->yuv.kg;
658
2.13k
    const float kb = state->yuv.kb;
659
2.13k
    float * unormFloatTableY = NULL;
660
2.13k
    float * unormFloatTableUV = NULL;
661
2.13k
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
662
2.13k
    const uint32_t yuvChannelBytes = state->yuv.channelBytes;
663
2.13k
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
664
665
    // Aliases for plane data
666
2.13k
    const uint8_t * yPlane = image->yuvPlanes[AVIF_CHAN_Y];
667
2.13k
    const uint8_t * uPlane = image->yuvPlanes[AVIF_CHAN_U];
668
2.13k
    const uint8_t * vPlane = image->yuvPlanes[AVIF_CHAN_V];
669
2.13k
    const uint8_t * aPlane = image->alphaPlane;
670
2.13k
    const uint32_t yRowBytes = image->yuvRowBytes[AVIF_CHAN_Y];
671
2.13k
    const uint32_t uRowBytes = image->yuvRowBytes[AVIF_CHAN_U];
672
2.13k
    const uint32_t vRowBytes = image->yuvRowBytes[AVIF_CHAN_V];
673
2.13k
    const uint32_t aRowBytes = image->alphaRowBytes;
674
675
    // Various observations and limits
676
2.13k
    const avifBool yuvHasColor = (uPlane && vPlane && (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400));
677
2.13k
    const avifBool rgbHasColor = !avifRGBFormatIsGray(rgb->format);
678
2.13k
    const uint16_t yuvMaxChannel = (uint16_t)state->yuv.maxChannel;
679
2.13k
    const float rgbMaxChannelF = state->rgb.maxChannelF;
680
681
    // If toRGBAlphaMode is active (not no-op), assert that the alpha plane is present. The end of
682
    // the avifPrepareReformatState() function should ensure this, but this assert makes it clear
683
    // to clang's analyzer.
684
2.13k
    assert((alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP) || aPlane);
685
686
3.20M
    for (uint32_t j = 0; j < image->height; ++j) {
687
        // uvJ is used only when yuvHasColor is true.
688
3.20M
        const uint32_t uvJ = yuvHasColor ? (j >> state->yuv.formatInfo.chromaShiftY) : 0;
689
3.20M
        const uint8_t * ptrY8 = &yPlane[j * yRowBytes];
690
3.20M
        const uint8_t * ptrU8 = uPlane ? &uPlane[(uvJ * uRowBytes)] : NULL;
691
3.20M
        const uint8_t * ptrV8 = vPlane ? &vPlane[(uvJ * vRowBytes)] : NULL;
692
3.20M
        const uint8_t * ptrA8 = aPlane ? &aPlane[j * aRowBytes] : NULL;
693
3.20M
        const uint16_t * ptrY16 = (const uint16_t *)ptrY8;
694
3.20M
        const uint16_t * ptrU16 = (const uint16_t *)ptrU8;
695
3.20M
        const uint16_t * ptrV16 = (const uint16_t *)ptrV8;
696
3.20M
        const uint16_t * ptrA16 = (const uint16_t *)ptrA8;
697
698
3.20M
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + ((size_t)j * rgb->rowBytes)];
699
3.20M
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + ((size_t)j * rgb->rowBytes)];
700
3.20M
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + ((size_t)j * rgb->rowBytes)];
701
3.20M
        uint8_t * ptrGray = &rgb->pixels[state->rgb.offsetBytesGray + ((size_t)j * rgb->rowBytes)];
702
703
436M
        for (uint32_t i = 0; i < image->width; ++i) {
704
433M
            float Y, Cb = 0.5f, Cr = 0.5f;
705
706
            // Calculate Y
707
433M
            uint16_t unormY;
708
433M
            if (image->depth == 8) {
709
205M
                unormY = ptrY8[i];
710
227M
            } else {
711
                // clamp incoming data to protect against bad LUT lookups
712
227M
                unormY = AVIF_MIN(ptrY16[i], yuvMaxChannel);
713
227M
            }
714
433M
            Y = unormFloatTableY[unormY];
715
716
            // Calculate Cb and Cr
717
433M
            if (yuvHasColor) {
718
390M
                const uint32_t uvI = i >> state->yuv.formatInfo.chromaShiftX;
719
390M
                if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) {
720
26.0M
                    uint16_t unormU, unormV;
721
722
26.0M
                    if (image->depth == 8) {
723
10.8M
                        unormU = ptrU8[uvI];
724
10.8M
                        unormV = ptrV8[uvI];
725
15.2M
                    } else {
726
                        // clamp incoming data to protect against bad LUT lookups
727
15.2M
                        unormU = AVIF_MIN(ptrU16[uvI], yuvMaxChannel);
728
15.2M
                        unormV = AVIF_MIN(ptrV16[uvI], yuvMaxChannel);
729
15.2M
                    }
730
731
26.0M
                    Cb = unormFloatTableUV[unormU];
732
26.0M
                    Cr = unormFloatTableUV[unormV];
733
364M
                } else {
734
                    // Upsample to 444:
735
                    //
736
                    // *   *   *   *
737
                    //   A       B
738
                    // *   1   2   *
739
                    //
740
                    // *   3   4   *
741
                    //   C       D
742
                    // *   *   *   *
743
                    //
744
                    // When converting from YUV420 to RGB, for any given "high-resolution" RGB
745
                    // coordinate (1,2,3,4,*), there are up to four "low-resolution" UV samples
746
                    // (A,B,C,D) that are "nearest" to the pixel. For RGB pixel #1, A is the closest
747
                    // UV sample, B and C are "adjacent" to it on the same row and column, and D is
748
                    // the diagonal. For RGB pixel 3, C is the closest UV sample, A and D are
749
                    // adjacent, and B is the diagonal. Sometimes the adjacent pixel on the same row
750
                    // is to the left or right, and sometimes the adjacent pixel on the same column
751
                    // is up or down. For any edge or corner, there might only be only one or two
752
                    // samples nearby, so they'll be duplicated.
753
                    //
754
                    // The following code attempts to find all four nearest UV samples and put them
755
                    // in the following unormU and unormV grid as follows:
756
                    //
757
                    // unorm[0][0] = closest         ( weights: bilinear: 9/16, nearest: 1 )
758
                    // unorm[1][0] = adjacent col    ( weights: bilinear: 3/16, nearest: 0 )
759
                    // unorm[0][1] = adjacent row    ( weights: bilinear: 3/16, nearest: 0 )
760
                    // unorm[1][1] = diagonal        ( weights: bilinear: 1/16, nearest: 0 )
761
                    //
762
                    // It then weights them according to the requested upsampling set in avifRGBImage.
763
764
364M
                    uint16_t unormU[2][2], unormV[2][2];
765
766
                    // How many bytes to add to a uint8_t pointer index to get to the adjacent (lesser) sample in a given direction
767
364M
                    int uAdjCol, vAdjCol, uAdjRow, vAdjRow;
768
364M
                    if ((i == 0) || ((i == (image->width - 1)) && ((i % 2) != 0))) {
769
3.31M
                        uAdjCol = 0;
770
3.31M
                        vAdjCol = 0;
771
361M
                    } else {
772
361M
                        if ((i % 2) != 0) {
773
186M
                            uAdjCol = yuvChannelBytes;
774
186M
                            vAdjCol = yuvChannelBytes;
775
186M
                        } else {
776
175M
                            uAdjCol = -1 * yuvChannelBytes;
777
175M
                            vAdjCol = -1 * yuvChannelBytes;
778
175M
                        }
779
361M
                    }
780
781
                    // For YUV422, uvJ will always be a fresh value (always corresponds to j), so
782
                    // we'll simply duplicate the sample as if we were on the top or bottom row and
783
                    // it'll behave as plain old linear (1D) upsampling, which is all we want.
784
364M
                    if ((j == 0) || ((j == (image->height - 1)) && ((j % 2) != 0)) || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422)) {
785
29.8M
                        uAdjRow = 0;
786
29.8M
                        vAdjRow = 0;
787
334M
                    } else {
788
334M
                        if ((j % 2) != 0) {
789
166M
                            uAdjRow = (int)uRowBytes;
790
166M
                            vAdjRow = (int)vRowBytes;
791
167M
                        } else {
792
167M
                            uAdjRow = -1 * (int)uRowBytes;
793
167M
                            vAdjRow = -1 * (int)vRowBytes;
794
167M
                        }
795
334M
                    }
796
797
364M
                    if (image->depth == 8) {
798
191M
                        unormU[0][0] = uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes)];
799
191M
                        unormV[0][0] = vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes)];
800
191M
                        unormU[1][0] = uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjCol];
801
191M
                        unormV[1][0] = vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol];
802
191M
                        unormU[0][1] = uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjRow];
803
191M
                        unormV[0][1] = vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjRow];
804
191M
                        unormU[1][1] = uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjCol + uAdjRow];
805
191M
                        unormV[1][1] = vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol + vAdjRow];
806
191M
                    } else {
807
172M
                        unormU[0][0] = *((const uint16_t *)&uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes)]);
808
172M
                        unormV[0][0] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes)]);
809
172M
                        unormU[1][0] = *((const uint16_t *)&uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjCol]);
810
172M
                        unormV[1][0] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol]);
811
172M
                        unormU[0][1] = *((const uint16_t *)&uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjRow]);
812
172M
                        unormV[0][1] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjRow]);
813
172M
                        unormU[1][1] = *((const uint16_t *)&uPlane[(uvJ * uRowBytes) + (uvI * yuvChannelBytes) + uAdjCol + uAdjRow]);
814
172M
                        unormV[1][1] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol + vAdjRow]);
815
816
                        // clamp incoming data to protect against bad LUT lookups
817
505M
                        for (int bJ = 0; bJ < 2; ++bJ) {
818
1.00G
                            for (int bI = 0; bI < 2; ++bI) {
819
668M
                                unormU[bI][bJ] = AVIF_MIN(unormU[bI][bJ], yuvMaxChannel);
820
668M
                                unormV[bI][bJ] = AVIF_MIN(unormV[bI][bJ], yuvMaxChannel);
821
668M
                            }
822
333M
                        }
823
172M
                    }
824
825
364M
                    if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) ||
826
360M
                        (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST)) {
827
                        // Nearest neighbor; ignore all UVs but the closest one
828
0
                        Cb = unormFloatTableUV[unormU[0][0]];
829
0
                        Cr = unormFloatTableUV[unormV[0][0]];
830
364M
                    } else {
831
                        // Bilinear filtering with weights
832
364M
                        Cb = (unormFloatTableUV[unormU[0][0]] * (9.0f / 16.0f)) + (unormFloatTableUV[unormU[1][0]] * (3.0f / 16.0f)) +
833
364M
                             (unormFloatTableUV[unormU[0][1]] * (3.0f / 16.0f)) + (unormFloatTableUV[unormU[1][1]] * (1.0f / 16.0f));
834
364M
                        Cr = (unormFloatTableUV[unormV[0][0]] * (9.0f / 16.0f)) + (unormFloatTableUV[unormV[1][0]] * (3.0f / 16.0f)) +
835
364M
                             (unormFloatTableUV[unormV[0][1]] * (3.0f / 16.0f)) + (unormFloatTableUV[unormV[1][1]] * (1.0f / 16.0f));
836
364M
                    }
837
364M
                }
838
390M
            }
839
840
433M
            float Rc = 0.0f, Gc = 0.0f, Bc = 0.0f, grayc = 0.0f;
841
433M
            if (rgbHasColor) {
842
432M
                float R, G, B;
843
432M
                if (yuvHasColor) {
844
387M
                    if (state->yuv.mode == AVIF_REFORMAT_MODE_IDENTITY) {
845
                        // Identity (GBR): Formulas 41,42,43 from
846
                        // https://www.itu.int/rec/T-REC-H.273-201612-S
847
12.6M
                        G = Y;
848
12.6M
                        B = Cb;
849
12.6M
                        R = Cr;
850
374M
                    } else if (state->yuv.mode == AVIF_REFORMAT_MODE_YCGCO) {
851
                        // YCgCo: Formulas 47,48,49,50 from
852
                        // https://www.itu.int/rec/T-REC-H.273-201612-S
853
10.5M
                        const float t = Y - Cb;
854
10.5M
                        G = Y + Cb;
855
10.5M
                        B = t - Cr;
856
10.5M
                        R = t + Cr;
857
364M
                    } else if ((state->yuv.mode == AVIF_REFORMAT_MODE_YCGCO_RE) || (state->yuv.mode == AVIF_REFORMAT_MODE_YCGCO_RO)) {
858
                        // YCgCoRe/YCgCoRo: Formulas 62,63,64,65 from
859
                        // https://www.itu.int/rec/T-REC-H.273-202407-P
860
2.99M
                        const int YY = unormY;
861
2.99M
                        const int Cg = (int)avifRoundf(Cb * yuvMaxChannel);
862
2.99M
                        const int Co = (int)avifRoundf(Cr * yuvMaxChannel);
863
2.99M
                        const int t = YY - (Cg >> 1);
864
2.99M
                        G = (float)AVIF_CLAMP(t + Cg, 0, state->rgb.maxChannel);
865
2.99M
                        B = (float)AVIF_CLAMP(t - (Co >> 1), 0, state->rgb.maxChannel);
866
2.99M
                        R = (float)AVIF_CLAMP(B + Co, 0, state->rgb.maxChannel);
867
2.99M
                        G /= rgbMaxChannelF;
868
2.99M
                        B /= rgbMaxChannelF;
869
2.99M
                        R /= rgbMaxChannelF;
870
361M
                    } else {
871
                        // Normal YUV
872
361M
                        R = Y + (2 * (1 - kr)) * Cr;
873
361M
                        B = Y + (2 * (1 - kb)) * Cb;
874
361M
                        G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
875
361M
                    }
876
387M
                } else {
877
                    // Monochrome: just populate all channels with luma (state->yuv.mode
878
                    // is irrelevant)
879
45.3M
                    R = Y;
880
45.3M
                    G = Y;
881
45.3M
                    B = Y;
882
45.3M
                }
883
432M
                Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
884
432M
                Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
885
432M
                Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
886
432M
            } else {
887
                // Monochrome: gray is luma
888
564k
                float gray = Y;
889
564k
                grayc = AVIF_CLAMP(gray, 0.0f, 1.0f);
890
564k
            }
891
892
433M
            if (alphaMultiplyMode != AVIF_ALPHA_MULTIPLY_MODE_NO_OP) {
893
                // Calculate A
894
0
                uint16_t unormA;
895
0
                if (image->depth == 8) {
896
0
                    unormA = ptrA8[i];
897
0
                } else {
898
0
                    unormA = AVIF_MIN(ptrA16[i], yuvMaxChannel);
899
0
                }
900
0
                const float A = unormA / ((float)state->yuv.maxChannel);
901
0
                const float Ac = AVIF_CLAMP(A, 0.0f, 1.0f);
902
903
0
                if (alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY) {
904
0
                    if (rgbHasColor) {
905
0
                        if (Ac == 0.0f) {
906
0
                            Rc = 0.0f;
907
0
                            Gc = 0.0f;
908
0
                            Bc = 0.0f;
909
0
                        } else if (Ac < 1.0f) {
910
0
                            Rc *= Ac;
911
0
                            Gc *= Ac;
912
0
                            Bc *= Ac;
913
0
                        }
914
0
                    } else {
915
0
                        if (Ac == 0.0f) {
916
0
                            grayc = 0.0f;
917
0
                        } else if (Ac < 1.0f) {
918
0
                            grayc *= Ac;
919
0
                        }
920
0
                    }
921
0
                } else {
922
                    // alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY
923
0
                    if (rgbHasColor) {
924
0
                        if (Ac == 0.0f) {
925
0
                            Rc = 0.0f;
926
0
                            Gc = 0.0f;
927
0
                            Bc = 0.0f;
928
0
                        } else if (Ac < 1.0f) {
929
0
                            Rc /= Ac;
930
0
                            Gc /= Ac;
931
0
                            Bc /= Ac;
932
0
                            Rc = AVIF_MIN(Rc, 1.0f);
933
0
                            Gc = AVIF_MIN(Gc, 1.0f);
934
0
                            Bc = AVIF_MIN(Bc, 1.0f);
935
0
                        }
936
0
                    } else {
937
0
                        if (Ac == 0.0f) {
938
0
                            grayc = 0.0f;
939
0
                        } else if (Ac < 1.0f) {
940
0
                            grayc /= Ac;
941
0
                            grayc = AVIF_MIN(grayc, 1.0f);
942
0
                        }
943
0
                    }
944
0
                }
945
0
            }
946
947
433M
            if (rgbHasColor) {
948
433M
                if (rgb->depth == 8) {
949
208M
                    avifStoreRGB8Pixel(rgb->format,
950
208M
                                       (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
951
208M
                                       (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
952
208M
                                       (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
953
208M
                                       ptrR,
954
208M
                                       ptrG,
955
208M
                                       ptrB);
956
225M
                } else {
957
225M
                    *((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * rgbMaxChannelF));
958
225M
                    *((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * rgbMaxChannelF));
959
225M
                    *((uint16_t *)ptrB) = (uint16_t)(0.5f + (Bc * rgbMaxChannelF));
960
225M
                }
961
433M
                ptrR += rgbPixelBytes;
962
433M
                ptrG += rgbPixelBytes;
963
433M
                ptrB += rgbPixelBytes;
964
18.4E
            } else {
965
18.4E
                if (rgb->depth == 8) {
966
0
                    *ptrGray = (uint8_t)(0.5f + (grayc * rgbMaxChannelF));
967
18.4E
                } else {
968
18.4E
                    *((uint16_t *)ptrGray) = (uint16_t)(0.5f + (grayc * rgbMaxChannelF));
969
18.4E
                }
970
18.4E
                ptrGray += rgbPixelBytes;
971
18.4E
            }
972
433M
        }
973
3.20M
    }
974
2.13k
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
975
2.13k
    return AVIF_RESULT_OK;
976
2.13k
}
977
978
static avifResult avifImageYUV16ToRGB16Color(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
979
1.43k
{
980
1.43k
    const float kr = state->yuv.kr;
981
1.43k
    const float kg = state->yuv.kg;
982
1.43k
    const float kb = state->yuv.kb;
983
1.43k
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
984
1.43k
    float * unormFloatTableY = NULL;
985
1.43k
    float * unormFloatTableUV = NULL;
986
1.43k
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
987
988
1.43k
    const uint16_t yuvMaxChannel = (uint16_t)state->yuv.maxChannel;
989
1.43k
    const float rgbMaxChannelF = state->rgb.maxChannelF;
990
359k
    for (size_t j = 0; j < image->height; ++j) {
991
358k
        const size_t uvJ = j >> state->yuv.formatInfo.chromaShiftY;
992
358k
        const uint16_t * const ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
993
358k
        const uint16_t * const ptrU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
994
358k
        const uint16_t * const ptrV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
995
358k
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
996
358k
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
997
358k
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
998
999
98.6M
        for (size_t i = 0; i < image->width; ++i) {
1000
98.3M
            size_t uvI = i >> state->yuv.formatInfo.chromaShiftX;
1001
1002
            // clamp incoming data to protect against bad LUT lookups
1003
98.3M
            const uint16_t unormY = AVIF_MIN(ptrY[i], yuvMaxChannel);
1004
98.3M
            const uint16_t unormU = AVIF_MIN(ptrU[uvI], yuvMaxChannel);
1005
98.3M
            const uint16_t unormV = AVIF_MIN(ptrV[uvI], yuvMaxChannel);
1006
1007
            // Convert unorm to float
1008
98.3M
            const float Y = unormFloatTableY[unormY];
1009
98.3M
            const float Cb = unormFloatTableUV[unormU];
1010
98.3M
            const float Cr = unormFloatTableUV[unormV];
1011
1012
98.3M
            const float R = Y + (2 * (1 - kr)) * Cr;
1013
98.3M
            const float B = Y + (2 * (1 - kb)) * Cb;
1014
98.3M
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1015
98.3M
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1016
98.3M
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1017
98.3M
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1018
1019
98.3M
            *((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * rgbMaxChannelF));
1020
98.3M
            *((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * rgbMaxChannelF));
1021
98.3M
            *((uint16_t *)ptrB) = (uint16_t)(0.5f + (Bc * rgbMaxChannelF));
1022
1023
98.3M
            ptrR += rgbPixelBytes;
1024
98.3M
            ptrG += rgbPixelBytes;
1025
98.3M
            ptrB += rgbPixelBytes;
1026
98.3M
        }
1027
358k
    }
1028
1.43k
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
1029
1.43k
    return AVIF_RESULT_OK;
1030
1.43k
}
1031
1032
static avifResult avifImageYUV16ToRGB16Mono(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1033
548
{
1034
548
    const float kr = state->yuv.kr;
1035
548
    const float kg = state->yuv.kg;
1036
548
    const float kb = state->yuv.kb;
1037
548
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1038
548
    float * unormFloatTableY = NULL;
1039
548
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1040
1041
548
    const uint16_t maxChannel = (uint16_t)state->yuv.maxChannel;
1042
548
    const float maxChannelF = state->rgb.maxChannelF;
1043
92.1k
    for (size_t j = 0; j < image->height; ++j) {
1044
91.6k
        const uint16_t * const ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1045
91.6k
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1046
91.6k
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1047
91.6k
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1048
1049
165M
        for (size_t i = 0; i < image->width; ++i) {
1050
            // clamp incoming data to protect against bad LUT lookups
1051
165M
            const uint16_t unormY = AVIF_MIN(ptrY[i], maxChannel);
1052
1053
            // Convert unorm to float
1054
165M
            const float Y = unormFloatTableY[unormY];
1055
165M
            const float Cb = 0.0f;
1056
165M
            const float Cr = 0.0f;
1057
1058
165M
            const float R = Y + (2 * (1 - kr)) * Cr;
1059
165M
            const float B = Y + (2 * (1 - kb)) * Cb;
1060
165M
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1061
165M
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1062
165M
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1063
165M
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1064
1065
165M
            *((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * maxChannelF));
1066
165M
            *((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * maxChannelF));
1067
165M
            *((uint16_t *)ptrB) = (uint16_t)(0.5f + (Bc * maxChannelF));
1068
1069
165M
            ptrR += rgbPixelBytes;
1070
165M
            ptrG += rgbPixelBytes;
1071
165M
            ptrB += rgbPixelBytes;
1072
165M
        }
1073
91.6k
    }
1074
548
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
1075
548
    return AVIF_RESULT_OK;
1076
548
}
1077
1078
static avifResult avifImageYUV16ToRGB8Color(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1079
0
{
1080
0
    const float kr = state->yuv.kr;
1081
0
    const float kg = state->yuv.kg;
1082
0
    const float kb = state->yuv.kb;
1083
0
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1084
0
    float * unormFloatTableY = NULL;
1085
0
    float * unormFloatTableUV = NULL;
1086
0
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1087
1088
0
    const uint16_t yuvMaxChannel = (uint16_t)state->yuv.maxChannel;
1089
0
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1090
0
    for (size_t j = 0; j < image->height; ++j) {
1091
0
        const size_t uvJ = j >> state->yuv.formatInfo.chromaShiftY;
1092
0
        const uint16_t * const ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1093
0
        const uint16_t * const ptrU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
1094
0
        const uint16_t * const ptrV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
1095
0
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1096
0
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1097
0
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1098
1099
0
        for (size_t i = 0; i < image->width; ++i) {
1100
0
            size_t uvI = i >> state->yuv.formatInfo.chromaShiftX;
1101
1102
            // clamp incoming data to protect against bad LUT lookups
1103
0
            const uint16_t unormY = AVIF_MIN(ptrY[i], yuvMaxChannel);
1104
0
            const uint16_t unormU = AVIF_MIN(ptrU[uvI], yuvMaxChannel);
1105
0
            const uint16_t unormV = AVIF_MIN(ptrV[uvI], yuvMaxChannel);
1106
1107
            // Convert unorm to float
1108
0
            const float Y = unormFloatTableY[unormY];
1109
0
            const float Cb = unormFloatTableUV[unormU];
1110
0
            const float Cr = unormFloatTableUV[unormV];
1111
1112
0
            const float R = Y + (2 * (1 - kr)) * Cr;
1113
0
            const float B = Y + (2 * (1 - kb)) * Cb;
1114
0
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1115
0
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1116
0
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1117
0
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1118
1119
0
            avifStoreRGB8Pixel(rgb->format,
1120
0
                               (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
1121
0
                               (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
1122
0
                               (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
1123
0
                               ptrR,
1124
0
                               ptrG,
1125
0
                               ptrB);
1126
1127
0
            ptrR += rgbPixelBytes;
1128
0
            ptrG += rgbPixelBytes;
1129
0
            ptrB += rgbPixelBytes;
1130
0
        }
1131
0
    }
1132
0
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
1133
0
    return AVIF_RESULT_OK;
1134
0
}
1135
1136
static avifResult avifImageYUV16ToRGB8Mono(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1137
0
{
1138
0
    const float kr = state->yuv.kr;
1139
0
    const float kg = state->yuv.kg;
1140
0
    const float kb = state->yuv.kb;
1141
0
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1142
0
    float * unormFloatTableY = NULL;
1143
0
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1144
1145
0
    const uint16_t yuvMaxChannel = (uint16_t)state->yuv.maxChannel;
1146
0
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1147
0
    for (size_t j = 0; j < image->height; ++j) {
1148
0
        const uint16_t * const ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1149
0
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1150
0
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1151
0
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1152
1153
0
        for (size_t i = 0; i < image->width; ++i) {
1154
            // clamp incoming data to protect against bad LUT lookups
1155
0
            const uint16_t unormY = AVIF_MIN(ptrY[i], yuvMaxChannel);
1156
1157
            // Convert unorm to float
1158
0
            const float Y = unormFloatTableY[unormY];
1159
0
            const float Cb = 0.0f;
1160
0
            const float Cr = 0.0f;
1161
1162
0
            const float R = Y + (2 * (1 - kr)) * Cr;
1163
0
            const float B = Y + (2 * (1 - kb)) * Cb;
1164
0
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1165
0
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1166
0
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1167
0
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1168
1169
0
            avifStoreRGB8Pixel(rgb->format,
1170
0
                               (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
1171
0
                               (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
1172
0
                               (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
1173
0
                               ptrR,
1174
0
                               ptrG,
1175
0
                               ptrB);
1176
1177
0
            ptrR += rgbPixelBytes;
1178
0
            ptrG += rgbPixelBytes;
1179
0
            ptrB += rgbPixelBytes;
1180
0
        }
1181
0
    }
1182
0
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
1183
0
    return AVIF_RESULT_OK;
1184
0
}
1185
1186
static avifResult avifImageYUV8ToRGB16Color(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1187
0
{
1188
0
    const float kr = state->yuv.kr;
1189
0
    const float kg = state->yuv.kg;
1190
0
    const float kb = state->yuv.kb;
1191
0
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1192
0
    float * unormFloatTableY = NULL;
1193
0
    float * unormFloatTableUV = NULL;
1194
0
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1195
1196
0
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1197
0
    for (size_t j = 0; j < image->height; ++j) {
1198
0
        const size_t uvJ = j >> state->yuv.formatInfo.chromaShiftY;
1199
0
        const uint8_t * const ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1200
0
        const uint8_t * const ptrU = &image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
1201
0
        const uint8_t * const ptrV = &image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
1202
0
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1203
0
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1204
0
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1205
1206
0
        for (size_t i = 0; i < image->width; ++i) {
1207
0
            size_t uvI = i >> state->yuv.formatInfo.chromaShiftX;
1208
1209
            // Convert unorm to float (no clamp necessary, the full uint8_t range is a legal lookup)
1210
0
            const float Y = unormFloatTableY[ptrY[i]];
1211
0
            const float Cb = unormFloatTableUV[ptrU[uvI]];
1212
0
            const float Cr = unormFloatTableUV[ptrV[uvI]];
1213
1214
0
            const float R = Y + (2 * (1 - kr)) * Cr;
1215
0
            const float B = Y + (2 * (1 - kb)) * Cb;
1216
0
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1217
0
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1218
0
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1219
0
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1220
1221
0
            *((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * rgbMaxChannelF));
1222
0
            *((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * rgbMaxChannelF));
1223
0
            *((uint16_t *)ptrB) = (uint16_t)(0.5f + (Bc * rgbMaxChannelF));
1224
1225
0
            ptrR += rgbPixelBytes;
1226
0
            ptrG += rgbPixelBytes;
1227
0
            ptrB += rgbPixelBytes;
1228
0
        }
1229
0
    }
1230
0
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
1231
0
    return AVIF_RESULT_OK;
1232
0
}
1233
1234
static avifResult avifImageYUV8ToRGB16Mono(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1235
0
{
1236
0
    const float kr = state->yuv.kr;
1237
0
    const float kg = state->yuv.kg;
1238
0
    const float kb = state->yuv.kb;
1239
0
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1240
0
    float * unormFloatTableY = NULL;
1241
0
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1242
1243
0
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1244
0
    for (size_t j = 0; j < image->height; ++j) {
1245
0
        const uint8_t * const ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1246
0
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1247
0
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1248
0
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1249
1250
0
        for (size_t i = 0; i < image->width; ++i) {
1251
            // Convert unorm to float (no clamp necessary, the full uint8_t range is a legal lookup)
1252
0
            const float Y = unormFloatTableY[ptrY[i]];
1253
0
            const float Cb = 0.0f;
1254
0
            const float Cr = 0.0f;
1255
1256
0
            const float R = Y + (2 * (1 - kr)) * Cr;
1257
0
            const float B = Y + (2 * (1 - kb)) * Cb;
1258
0
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1259
0
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1260
0
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1261
0
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1262
1263
0
            *((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * rgbMaxChannelF));
1264
0
            *((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * rgbMaxChannelF));
1265
0
            *((uint16_t *)ptrB) = (uint16_t)(0.5f + (Bc * rgbMaxChannelF));
1266
1267
0
            ptrR += rgbPixelBytes;
1268
0
            ptrG += rgbPixelBytes;
1269
0
            ptrB += rgbPixelBytes;
1270
0
        }
1271
0
    }
1272
0
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
1273
0
    return AVIF_RESULT_OK;
1274
0
}
1275
1276
static avifResult avifImageIdentity8ToRGB8ColorFullRange(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1277
6.50k
{
1278
6.50k
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1279
740k
    for (size_t j = 0; j < image->height; ++j) {
1280
733k
        const uint8_t * const ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1281
733k
        const uint8_t * const ptrU = &image->yuvPlanes[AVIF_CHAN_U][(j * image->yuvRowBytes[AVIF_CHAN_U])];
1282
733k
        const uint8_t * const ptrV = &image->yuvPlanes[AVIF_CHAN_V][(j * image->yuvRowBytes[AVIF_CHAN_V])];
1283
733k
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1284
733k
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1285
733k
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1286
1287
        // This is intentionally a per-row conditional instead of a per-pixel
1288
        // conditional. This makes the "else" path (much more common than the
1289
        // "if" path) much faster than having a per-pixel branch.
1290
733k
        if (rgb->format == AVIF_RGB_FORMAT_RGB_565) {
1291
0
            for (size_t i = 0; i < image->width; ++i) {
1292
0
                *(uint16_t *)ptrR = RGB565(ptrV[i], ptrY[i], ptrU[i]);
1293
0
                ptrR += rgbPixelBytes;
1294
0
            }
1295
733k
        } else {
1296
109M
            for (size_t i = 0; i < image->width; ++i) {
1297
108M
                *ptrR = ptrV[i];
1298
108M
                *ptrG = ptrY[i];
1299
108M
                *ptrB = ptrU[i];
1300
108M
                ptrR += rgbPixelBytes;
1301
108M
                ptrG += rgbPixelBytes;
1302
108M
                ptrB += rgbPixelBytes;
1303
108M
            }
1304
733k
        }
1305
733k
    }
1306
6.50k
    return AVIF_RESULT_OK;
1307
6.50k
}
1308
1309
static avifResult avifImageYUV8ToRGB8Color(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1310
1.46k
{
1311
1.46k
    const float kr = state->yuv.kr;
1312
1.46k
    const float kg = state->yuv.kg;
1313
1.46k
    const float kb = state->yuv.kb;
1314
1.46k
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1315
1.46k
    float * unormFloatTableY = NULL;
1316
1.46k
    float * unormFloatTableUV = NULL;
1317
1.46k
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1318
1319
1.46k
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1320
744k
    for (size_t j = 0; j < image->height; ++j) {
1321
743k
        const size_t uvJ = j >> state->yuv.formatInfo.chromaShiftY;
1322
743k
        const uint8_t * const ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1323
743k
        const uint8_t * const ptrU = &image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
1324
743k
        const uint8_t * const ptrV = &image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
1325
743k
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1326
743k
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1327
743k
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1328
1329
149M
        for (size_t i = 0; i < image->width; ++i) {
1330
148M
            size_t uvI = i >> state->yuv.formatInfo.chromaShiftX;
1331
1332
            // Convert unorm to float (no clamp necessary, the full uint8_t range is a legal lookup)
1333
148M
            const float Y = unormFloatTableY[ptrY[i]];
1334
148M
            const float Cb = unormFloatTableUV[ptrU[uvI]];
1335
148M
            const float Cr = unormFloatTableUV[ptrV[uvI]];
1336
1337
148M
            const float R = Y + (2 * (1 - kr)) * Cr;
1338
148M
            const float B = Y + (2 * (1 - kb)) * Cb;
1339
148M
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1340
148M
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1341
148M
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1342
148M
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1343
1344
148M
            avifStoreRGB8Pixel(rgb->format,
1345
148M
                               (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
1346
148M
                               (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
1347
148M
                               (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
1348
148M
                               ptrR,
1349
148M
                               ptrG,
1350
148M
                               ptrB);
1351
1352
148M
            ptrR += rgbPixelBytes;
1353
148M
            ptrG += rgbPixelBytes;
1354
148M
            ptrB += rgbPixelBytes;
1355
148M
        }
1356
743k
    }
1357
1.46k
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, &unormFloatTableUV);
1358
1.46k
    return AVIF_RESULT_OK;
1359
1.46k
}
1360
1361
static avifResult avifImageYUV8ToRGB8Mono(const avifImage * image, avifRGBImage * rgb, avifReformatState * state)
1362
729
{
1363
729
    const float kr = state->yuv.kr;
1364
729
    const float kg = state->yuv.kg;
1365
729
    const float kb = state->yuv.kb;
1366
729
    const uint32_t rgbPixelBytes = state->rgb.pixelBytes;
1367
729
    float * unormFloatTableY = NULL;
1368
729
    AVIF_CHECKERR(avifCreateYUVToRGBLookUpTables(&unormFloatTableY, NULL, image->depth, state), AVIF_RESULT_OUT_OF_MEMORY);
1369
1370
729
    const float rgbMaxChannelF = state->rgb.maxChannelF;
1371
255k
    for (size_t j = 0; j < image->height; ++j) {
1372
254k
        const uint8_t * const ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
1373
254k
        uint8_t * ptrR = &rgb->pixels[state->rgb.offsetBytesR + (j * rgb->rowBytes)];
1374
254k
        uint8_t * ptrG = &rgb->pixels[state->rgb.offsetBytesG + (j * rgb->rowBytes)];
1375
254k
        uint8_t * ptrB = &rgb->pixels[state->rgb.offsetBytesB + (j * rgb->rowBytes)];
1376
1377
461M
        for (size_t i = 0; i < image->width; ++i) {
1378
            // Convert unorm to float (no clamp necessary, the full uint8_t range is a legal lookup)
1379
461M
            const float Y = unormFloatTableY[ptrY[i]];
1380
461M
            const float Cb = 0.0f;
1381
461M
            const float Cr = 0.0f;
1382
1383
461M
            const float R = Y + (2 * (1 - kr)) * Cr;
1384
461M
            const float B = Y + (2 * (1 - kb)) * Cb;
1385
461M
            const float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
1386
461M
            const float Rc = AVIF_CLAMP(R, 0.0f, 1.0f);
1387
461M
            const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
1388
461M
            const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
1389
1390
461M
            avifStoreRGB8Pixel(rgb->format,
1391
461M
                               (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
1392
461M
                               (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
1393
461M
                               (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
1394
461M
                               ptrR,
1395
461M
                               ptrG,
1396
461M
                               ptrB);
1397
1398
461M
            ptrR += rgbPixelBytes;
1399
461M
            ptrG += rgbPixelBytes;
1400
461M
            ptrB += rgbPixelBytes;
1401
461M
        }
1402
254k
    }
1403
729
    avifFreeYUVToRGBLookUpTables(&unormFloatTableY, NULL);
1404
729
    return AVIF_RESULT_OK;
1405
729
}
1406
1407
// This constant comes from libyuv. For details, see here:
1408
// https://chromium.googlesource.com/libyuv/libyuv/+/2f87e9a7/source/row_common.cc#3537
1409
0
#define F16_MULTIPLIER 1.9259299444e-34f
1410
1411
typedef union avifF16
1412
{
1413
    float f;
1414
    uint32_t u32;
1415
} avifF16;
1416
1417
static avifResult avifRGBImageToF16(avifRGBImage * rgb)
1418
0
{
1419
0
    avifResult libyuvResult = AVIF_RESULT_NOT_IMPLEMENTED;
1420
0
    if (!rgb->avoidLibYUV) {
1421
0
        libyuvResult = avifRGBImageToF16LibYUV(rgb);
1422
0
    }
1423
0
    if (libyuvResult != AVIF_RESULT_NOT_IMPLEMENTED) {
1424
0
        return libyuvResult;
1425
0
    }
1426
0
    const size_t channelCount = avifRGBFormatChannelCount(rgb->format);
1427
0
    const float scale = 1.0f / ((1 << rgb->depth) - 1);
1428
0
    const float multiplier = F16_MULTIPLIER * scale;
1429
0
    uint16_t * pixelRowBase = (uint16_t *)rgb->pixels;
1430
0
    const uint32_t stride = rgb->rowBytes >> 1;
1431
0
    for (size_t j = 0; j < rgb->height; ++j) {
1432
0
        uint16_t * pixel = pixelRowBase;
1433
0
        for (size_t i = 0; i < rgb->width * channelCount; ++i, ++pixel) {
1434
0
            avifF16 f16;
1435
0
            f16.f = *pixel * multiplier;
1436
0
            *pixel = (uint16_t)(f16.u32 >> 13);
1437
0
        }
1438
0
        pixelRowBase += stride;
1439
0
    }
1440
0
    return AVIF_RESULT_OK;
1441
0
}
1442
1443
static avifResult avifImageYUVToRGBImpl(const avifImage * image, avifRGBImage * rgb, avifReformatState * state, avifAlphaMultiplyMode alphaMultiplyMode)
1444
12.8k
{
1445
12.8k
    avifBool convertedWithLibYUV = AVIF_FALSE;
1446
    // Reformat alpha, if user asks for it, or (un)multiply processing needs it.
1447
12.8k
    avifBool reformatAlpha = avifRGBFormatHasAlpha(rgb->format) &&
1448
12.8k
                             (!rgb->ignoreAlpha || (alphaMultiplyMode != AVIF_ALPHA_MULTIPLY_MODE_NO_OP));
1449
    // This value is used only when reformatAlpha is true.
1450
12.8k
    avifBool alphaReformattedWithLibYUV = AVIF_FALSE;
1451
12.8k
    if (!rgb->avoidLibYUV && ((alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP) || avifRGBFormatHasAlpha(rgb->format))) {
1452
12.8k
        avifResult libyuvResult = avifImageYUVToRGBLibYUV(image, rgb, reformatAlpha, &alphaReformattedWithLibYUV);
1453
12.8k
        if (libyuvResult == AVIF_RESULT_OK) {
1454
0
            convertedWithLibYUV = AVIF_TRUE;
1455
12.8k
        } else {
1456
12.8k
            if (libyuvResult != AVIF_RESULT_NOT_IMPLEMENTED) {
1457
0
                return libyuvResult;
1458
0
            }
1459
12.8k
        }
1460
12.8k
    }
1461
1462
12.8k
    if (reformatAlpha && !alphaReformattedWithLibYUV) {
1463
12.7k
        avifAlphaParams params;
1464
1465
12.7k
        params.width = rgb->width;
1466
12.7k
        params.height = rgb->height;
1467
12.7k
        params.dstDepth = rgb->depth;
1468
12.7k
        params.dstPlane = rgb->pixels;
1469
12.7k
        params.dstRowBytes = rgb->rowBytes;
1470
12.7k
        params.dstOffsetBytes = state->rgb.offsetBytesA;
1471
12.7k
        params.dstPixelBytes = state->rgb.pixelBytes;
1472
1473
12.7k
        if (image->alphaPlane && image->alphaRowBytes) {
1474
251
            params.srcDepth = image->depth;
1475
251
            params.srcPlane = image->alphaPlane;
1476
251
            params.srcRowBytes = image->alphaRowBytes;
1477
251
            params.srcOffsetBytes = 0;
1478
251
            params.srcPixelBytes = state->yuv.channelBytes;
1479
1480
251
            avifReformatAlpha(&params);
1481
12.5k
        } else {
1482
12.5k
            avifFillAlpha(&params);
1483
12.5k
        }
1484
12.7k
    }
1485
1486
12.8k
    if (!convertedWithLibYUV) {
1487
        // libyuv is either unavailable or unable to perform the specific conversion required here.
1488
        // Look over the available built-in "fast" routines for YUV->RGB conversion and see if one
1489
        // fits the current combination, or as a last resort, call avifImageYUVAnyToRGBAnySlow(),
1490
        // which handles every possibly YUV->RGB combination, but very slowly (in comparison).
1491
1492
12.8k
        avifResult convertResult = AVIF_RESULT_NOT_IMPLEMENTED;
1493
1494
12.8k
        const avifBool hasColor =
1495
12.8k
            (image->yuvRowBytes[AVIF_CHAN_U] && image->yuvRowBytes[AVIF_CHAN_V] && (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV400));
1496
1497
12.8k
        if (!avifRGBFormatIsGray(rgb->format) &&
1498
12.8k
            (!hasColor || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) ||
1499
1.00k
             ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST))) &&
1500
11.8k
            (alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_NO_OP || avifRGBFormatHasAlpha(rgb->format))) {
1501
            // Explanations on the above conditional:
1502
            // * None of these fast paths currently support bilinear upsampling, so avoid all of them
1503
            //   unless the YUV data isn't subsampled or they explicitly requested AVIF_CHROMA_UPSAMPLING_NEAREST.
1504
            // * None of these fast paths currently handle alpha (un)multiply, so avoid all of them
1505
            //   if we can't do alpha (un)multiply as a separated post step (destination format doesn't have alpha).
1506
1507
11.8k
            if (state->yuv.mode == AVIF_REFORMAT_MODE_IDENTITY) {
1508
6.88k
                if ((image->depth == 8) && (rgb->depth == 8) && (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) &&
1509
6.53k
                    (image->yuvRange == AVIF_RANGE_FULL)) {
1510
6.50k
                    convertResult = avifImageIdentity8ToRGB8ColorFullRange(image, rgb, state);
1511
6.50k
                }
1512
1513
                // TODO: Add more fast paths for identity
1514
6.88k
            } else if (state->yuv.mode == AVIF_REFORMAT_MODE_YUV_COEFFICIENTS) {
1515
4.16k
                if (image->depth > 8) {
1516
                    // yuv:u16
1517
1518
1.97k
                    if (rgb->depth > 8) {
1519
                        // yuv:u16, rgb:u16
1520
1521
1.97k
                        if (hasColor) {
1522
1.43k
                            convertResult = avifImageYUV16ToRGB16Color(image, rgb, state);
1523
1.43k
                        } else {
1524
548
                            convertResult = avifImageYUV16ToRGB16Mono(image, rgb, state);
1525
548
                        }
1526
1.97k
                    } else {
1527
                        // yuv:u16, rgb:u8
1528
1529
0
                        if (hasColor) {
1530
0
                            convertResult = avifImageYUV16ToRGB8Color(image, rgb, state);
1531
0
                        } else {
1532
0
                            convertResult = avifImageYUV16ToRGB8Mono(image, rgb, state);
1533
0
                        }
1534
0
                    }
1535
2.19k
                } else {
1536
                    // yuv:u8
1537
1538
2.19k
                    if (rgb->depth > 8) {
1539
                        // yuv:u8, rgb:u16
1540
1541
0
                        if (hasColor) {
1542
0
                            convertResult = avifImageYUV8ToRGB16Color(image, rgb, state);
1543
0
                        } else {
1544
0
                            convertResult = avifImageYUV8ToRGB16Mono(image, rgb, state);
1545
0
                        }
1546
2.19k
                    } else {
1547
                        // yuv:u8, rgb:u8
1548
1549
2.19k
                        if (hasColor) {
1550
1.46k
                            convertResult = avifImageYUV8ToRGB8Color(image, rgb, state);
1551
1.46k
                        } else {
1552
729
                            convertResult = avifImageYUV8ToRGB8Mono(image, rgb, state);
1553
729
                        }
1554
2.19k
                    }
1555
2.19k
                }
1556
4.16k
            }
1557
11.8k
        }
1558
1559
12.8k
        if (convertResult == AVIF_RESULT_NOT_IMPLEMENTED) {
1560
            // If we get here, there is no fast path for this combination. Time to be slow!
1561
2.13k
            convertResult = avifImageYUVAnyToRGBAnySlow(image, rgb, state, alphaMultiplyMode);
1562
1563
            // The slow path also handles alpha (un)multiply, so forget the operation here.
1564
2.13k
            alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP;
1565
2.13k
        }
1566
1567
12.8k
        if (convertResult != AVIF_RESULT_OK) {
1568
0
            return convertResult;
1569
0
        }
1570
12.8k
    }
1571
1572
    // Process alpha premultiplication, if necessary
1573
12.8k
    if (alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY) {
1574
0
        avifResult result = avifRGBImagePremultiplyAlpha(rgb);
1575
0
        if (result != AVIF_RESULT_OK) {
1576
0
            return result;
1577
0
        }
1578
12.8k
    } else if (alphaMultiplyMode == AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY) {
1579
0
        avifResult result = avifRGBImageUnpremultiplyAlpha(rgb);
1580
0
        if (result != AVIF_RESULT_OK) {
1581
0
            return result;
1582
0
        }
1583
0
    }
1584
1585
    // Convert pixels to half floats (F16), if necessary.
1586
12.8k
    if (rgb->isFloat) {
1587
0
        return avifRGBImageToF16(rgb);
1588
0
    }
1589
1590
12.8k
    return AVIF_RESULT_OK;
1591
12.8k
}
1592
1593
typedef struct
1594
{
1595
#if defined(_WIN32)
1596
    HANDLE thread;
1597
#else
1598
    pthread_t thread;
1599
#endif
1600
    avifImage image;
1601
    avifRGBImage rgb;
1602
    avifReformatState * state;
1603
    avifAlphaMultiplyMode alphaMultiplyMode;
1604
    avifResult result;
1605
    avifBool threadCreated;
1606
} YUVToRGBThreadData;
1607
1608
#if defined(_WIN32)
1609
static unsigned int __stdcall avifImageYUVToRGBThreadWorker(void * arg)
1610
#else
1611
static void * avifImageYUVToRGBThreadWorker(void * arg)
1612
#endif
1613
12.0k
{
1614
12.0k
    YUVToRGBThreadData * data = (YUVToRGBThreadData *)arg;
1615
12.0k
    data->result = avifImageYUVToRGBImpl(&data->image, &data->rgb, data->state, data->alphaMultiplyMode);
1616
#if defined(_WIN32)
1617
    return 0;
1618
#else
1619
12.0k
    return NULL;
1620
12.0k
#endif
1621
12.0k
}
1622
1623
static avifBool avifCreateYUVToRGBThread(YUVToRGBThreadData * tdata)
1624
10.5k
{
1625
#if defined(_WIN32)
1626
    tdata->thread = (HANDLE)_beginthreadex(/*security=*/NULL,
1627
                                           /*stack_size=*/0,
1628
                                           &avifImageYUVToRGBThreadWorker,
1629
                                           tdata,
1630
                                           /*initflag=*/0,
1631
                                           /*thrdaddr=*/NULL);
1632
    return tdata->thread != NULL;
1633
#else
1634
10.5k
    return pthread_create(&tdata->thread, NULL, &avifImageYUVToRGBThreadWorker, tdata) == 0;
1635
10.5k
#endif
1636
10.5k
}
1637
1638
static avifBool avifJoinYUVToRGBThread(YUVToRGBThreadData * tdata)
1639
10.5k
{
1640
#if defined(_WIN32)
1641
    return WaitForSingleObject(tdata->thread, INFINITE) == WAIT_OBJECT_0 && CloseHandle(tdata->thread) != 0;
1642
#else
1643
10.5k
    return pthread_join(tdata->thread, NULL) == 0;
1644
10.5k
#endif
1645
10.5k
}
1646
1647
avifResult avifImageYUVToRGB(const avifImage * image, avifRGBImage * rgb)
1648
2.38k
{
1649
    // It is okay for rgb->maxThreads to be equal to zero in order to allow clients to zero initialize the avifRGBImage struct
1650
    // with memset.
1651
2.38k
    if (!image->yuvPlanes[AVIF_CHAN_Y] || rgb->maxThreads < 0) {
1652
0
        return AVIF_RESULT_REFORMAT_FAILED;
1653
0
    }
1654
1655
2.38k
    avifReformatState state;
1656
2.38k
    if (!avifPrepareReformatState(image, rgb, &state)) {
1657
159
        return AVIF_RESULT_REFORMAT_FAILED;
1658
159
    }
1659
1660
2.22k
    avifAlphaMultiplyMode alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP;
1661
2.22k
    if (image->alphaPlane) {
1662
48
        if (!avifRGBFormatHasAlpha(rgb->format) || rgb->ignoreAlpha) {
1663
            // if we are converting some image with alpha into a format without alpha, we should do 'premultiply alpha' before
1664
            // discarding alpha plane. This has the same effect of rendering this image on a black background, which makes sense.
1665
0
            if (!image->alphaPremultiplied) {
1666
0
                alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY;
1667
0
            }
1668
48
        } else {
1669
48
            if (!image->alphaPremultiplied && rgb->alphaPremultiplied) {
1670
0
                alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY;
1671
48
            } else if (image->alphaPremultiplied && !rgb->alphaPremultiplied) {
1672
0
                alphaMultiplyMode = AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY;
1673
0
            }
1674
48
        }
1675
48
    }
1676
1677
    // In practice, we rarely need more than 8 threads for YUV to RGB conversion.
1678
2.22k
    uint32_t jobs = AVIF_CLAMP(rgb->maxThreads, 1, 8);
1679
1680
    // When yuv format is 420 and chromaUpsampling could be BILINEAR, there is a dependency across the horizontal borders of each
1681
    // job. So we disallow multithreading in that case.
1682
2.22k
    if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 && (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_AUTOMATIC ||
1683
0
                                                         rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BEST_QUALITY ||
1684
217
                                                         rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BILINEAR)) {
1685
217
        jobs = 1;
1686
217
    }
1687
1688
    // Each thread worker needs at least 2 Y rows (to account for potential U/V subsampling).
1689
2.22k
    if (jobs == 1 || (image->height / 2) < jobs) {
1690
706
        return avifImageYUVToRGBImpl(image, rgb, &state, alphaMultiplyMode);
1691
706
    }
1692
1693
1.51k
    const size_t byteCount = sizeof(YUVToRGBThreadData) * jobs;
1694
1.51k
    YUVToRGBThreadData * threadData = (YUVToRGBThreadData *)avifAlloc(byteCount);
1695
1.51k
    if (!threadData) {
1696
0
        return AVIF_RESULT_OUT_OF_MEMORY;
1697
0
    }
1698
1.51k
    memset(threadData, 0, byteCount);
1699
1.51k
    uint32_t rowsPerJob = image->height / jobs;
1700
1.51k
    if (rowsPerJob % 2) {
1701
248
        ++rowsPerJob;
1702
248
        jobs = (image->height + rowsPerJob - 1) / rowsPerJob; // ceil
1703
248
    }
1704
1.51k
    const uint32_t rowsForLastJob = image->height - rowsPerJob * (jobs - 1);
1705
1.51k
    uint32_t startRow = 0;
1706
1.51k
    uint32_t i;
1707
13.6k
    for (i = 0; i < jobs; ++i, startRow += rowsPerJob) {
1708
12.0k
        YUVToRGBThreadData * tdata = &threadData[i];
1709
12.0k
        const avifCropRect rect = { .x = 0, .y = startRow, .width = image->width, .height = (i == jobs - 1) ? rowsForLastJob : rowsPerJob };
1710
12.0k
        if (avifImageSetViewRect(&tdata->image, image, &rect) != AVIF_RESULT_OK) {
1711
0
            tdata->result = AVIF_RESULT_REFORMAT_FAILED;
1712
0
            break;
1713
0
        }
1714
1715
12.0k
        tdata->rgb = *rgb;
1716
12.0k
        tdata->rgb.pixels += startRow * (size_t)rgb->rowBytes;
1717
12.0k
        tdata->rgb.height = tdata->image.height;
1718
1719
12.0k
        tdata->state = &state;
1720
12.0k
        tdata->alphaMultiplyMode = alphaMultiplyMode;
1721
1722
12.0k
        if (i > 0) {
1723
10.5k
            tdata->threadCreated = avifCreateYUVToRGBThread(tdata);
1724
10.5k
            if (!tdata->threadCreated) {
1725
0
                tdata->result = AVIF_RESULT_REFORMAT_FAILED;
1726
0
                break;
1727
0
            }
1728
10.5k
        }
1729
12.0k
    }
1730
    // If above loop ran successfully, run the first job in the current thread.
1731
1.51k
    if (i == jobs) {
1732
1.51k
        avifImageYUVToRGBThreadWorker(&threadData[0]);
1733
1.51k
    }
1734
1.51k
    avifResult result = AVIF_RESULT_OK;
1735
13.6k
    for (i = 0; i < jobs; ++i) {
1736
12.0k
        YUVToRGBThreadData * tdata = &threadData[i];
1737
12.0k
        if (tdata->threadCreated && !avifJoinYUVToRGBThread(tdata)) {
1738
0
            result = AVIF_RESULT_REFORMAT_FAILED;
1739
0
        }
1740
12.0k
        if (tdata->result != AVIF_RESULT_OK) {
1741
0
            result = tdata->result;
1742
0
        }
1743
12.0k
    }
1744
1.51k
    avifFree(threadData);
1745
1.51k
    return result;
1746
1.51k
}
1747
1748
// Limited -> Full
1749
// Plan: subtract limited offset, then multiply by ratio of FULLSIZE/LIMITEDSIZE (rounding), then clamp.
1750
// RATIO = (FULLY - 0) / (MAXLIMITEDY - MINLIMITEDY)
1751
// -----------------------------------------
1752
// ( ( (v - MINLIMITEDY)                    | subtract limited offset
1753
//     * FULLY                              | multiply numerator of ratio
1754
//   ) + ((MAXLIMITEDY - MINLIMITEDY) / 2)  | add 0.5 (half of denominator) to round
1755
// ) / (MAXLIMITEDY - MINLIMITEDY)          | divide by denominator of ratio
1756
// AVIF_CLAMP(v, 0, FULLY)                  | clamp to full range
1757
// -----------------------------------------
1758
#define LIMITED_TO_FULL(MINLIMITEDY, MAXLIMITEDY, FULLY)                                                 \
1759
103k
    v = (((v - MINLIMITEDY) * FULLY) + ((MAXLIMITEDY - MINLIMITEDY) / 2)) / (MAXLIMITEDY - MINLIMITEDY); \
1760
103k
    v = AVIF_CLAMP(v, 0, FULLY)
1761
1762
// Full -> Limited
1763
// Plan: multiply by ratio of LIMITEDSIZE/FULLSIZE (rounding), then add limited offset, then clamp.
1764
// RATIO = (MAXLIMITEDY - MINLIMITEDY) / (FULLY - 0)
1765
// -----------------------------------------
1766
// ( ( (v * (MAXLIMITEDY - MINLIMITEDY))    | multiply numerator of ratio
1767
//     + (FULLY / 2)                        | add 0.5 (half of denominator) to round
1768
//   ) / FULLY                              | divide by denominator of ratio
1769
// ) + MINLIMITEDY                          | add limited offset
1770
//  AVIF_CLAMP(v, MINLIMITEDY, MAXLIMITEDY) | clamp to limited range
1771
// -----------------------------------------
1772
#define FULL_TO_LIMITED(MINLIMITEDY, MAXLIMITEDY, FULLY)                           \
1773
0
    v = (((v * (MAXLIMITEDY - MINLIMITEDY)) + (FULLY / 2)) / FULLY) + MINLIMITEDY; \
1774
0
    v = AVIF_CLAMP(v, MINLIMITEDY, MAXLIMITEDY)
1775
1776
int avifLimitedToFullY(uint32_t depth, int v)
1777
103k
{
1778
103k
    switch (depth) {
1779
88.8k
        case 8:
1780
88.8k
            LIMITED_TO_FULL(16, 235, 255);
1781
88.8k
            break;
1782
15.0k
        case 10:
1783
15.0k
            LIMITED_TO_FULL(64, 940, 1023);
1784
15.0k
            break;
1785
0
        case 12:
1786
0
            LIMITED_TO_FULL(256, 3760, 4095);
1787
0
            break;
1788
103k
    }
1789
103k
    return v;
1790
103k
}
1791
1792
int avifLimitedToFullUV(uint32_t depth, int v)
1793
0
{
1794
0
    switch (depth) {
1795
0
        case 8:
1796
0
            LIMITED_TO_FULL(16, 240, 255);
1797
0
            break;
1798
0
        case 10:
1799
0
            LIMITED_TO_FULL(64, 960, 1023);
1800
0
            break;
1801
0
        case 12:
1802
0
            LIMITED_TO_FULL(256, 3840, 4095);
1803
0
            break;
1804
0
    }
1805
0
    return v;
1806
0
}
1807
1808
int avifFullToLimitedY(uint32_t depth, int v)
1809
0
{
1810
0
    switch (depth) {
1811
0
        case 8:
1812
0
            FULL_TO_LIMITED(16, 235, 255);
1813
0
            break;
1814
0
        case 10:
1815
0
            FULL_TO_LIMITED(64, 940, 1023);
1816
0
            break;
1817
0
        case 12:
1818
0
            FULL_TO_LIMITED(256, 3760, 4095);
1819
0
            break;
1820
0
    }
1821
0
    return v;
1822
0
}
1823
1824
int avifFullToLimitedUV(uint32_t depth, int v)
1825
0
{
1826
0
    switch (depth) {
1827
0
        case 8:
1828
0
            FULL_TO_LIMITED(16, 240, 255);
1829
0
            break;
1830
0
        case 10:
1831
0
            FULL_TO_LIMITED(64, 960, 1023);
1832
0
            break;
1833
0
        case 12:
1834
0
            FULL_TO_LIMITED(256, 3840, 4095);
1835
0
            break;
1836
0
    }
1837
0
    return v;
1838
0
}
1839
1840
static inline uint16_t avifFloatToF16(float v)
1841
0
{
1842
0
    avifF16 f16;
1843
0
    f16.f = v * F16_MULTIPLIER;
1844
0
    return (uint16_t)(f16.u32 >> 13);
1845
0
}
1846
1847
static inline float avifF16ToFloat(uint16_t v)
1848
0
{
1849
0
    avifF16 f16;
1850
0
    f16.u32 = v << 13;
1851
0
    return f16.f / F16_MULTIPLIER;
1852
0
}
1853
1854
void avifGetRGBAPixel(const avifRGBImage * src, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, float rgbaPixel[4])
1855
0
{
1856
0
    assert(src != NULL);
1857
0
    assert(!src->isFloat || src->depth == 16);
1858
0
    assert(src->format != AVIF_RGB_FORMAT_RGB_565 || src->depth == 8);
1859
1860
0
    const uint8_t * const srcPixel = &src->pixels[(size_t)y * src->rowBytes + (size_t)x * info->pixelBytes];
1861
0
    if (info->channelBytes > 1) {
1862
0
        uint16_t r = *((const uint16_t *)(&srcPixel[info->offsetBytesR]));
1863
0
        uint16_t g = *((const uint16_t *)(&srcPixel[info->offsetBytesG]));
1864
0
        uint16_t b = *((const uint16_t *)(&srcPixel[info->offsetBytesB]));
1865
0
        uint16_t a = avifRGBFormatHasAlpha(src->format) ? *((const uint16_t *)(&srcPixel[info->offsetBytesA])) : (uint16_t)info->maxChannel;
1866
0
        if (src->isFloat) {
1867
0
            rgbaPixel[0] = avifF16ToFloat(r);
1868
0
            rgbaPixel[1] = avifF16ToFloat(g);
1869
0
            rgbaPixel[2] = avifF16ToFloat(b);
1870
0
            rgbaPixel[3] = avifRGBFormatHasAlpha(src->format) ? avifF16ToFloat(a) : 1.0f;
1871
0
        } else {
1872
0
            rgbaPixel[0] = r / info->maxChannelF;
1873
0
            rgbaPixel[1] = g / info->maxChannelF;
1874
0
            rgbaPixel[2] = b / info->maxChannelF;
1875
0
            rgbaPixel[3] = a / info->maxChannelF;
1876
0
        }
1877
0
    } else {
1878
0
        if (src->format == AVIF_RGB_FORMAT_RGB_565) {
1879
0
            uint8_t r, g, b;
1880
0
            avifGetRGB565(&srcPixel[info->offsetBytesR], &r, &g, &b);
1881
0
            rgbaPixel[0] = r / info->maxChannelF;
1882
0
            rgbaPixel[1] = g / info->maxChannelF;
1883
0
            rgbaPixel[2] = b / info->maxChannelF;
1884
0
            rgbaPixel[3] = 1.0f;
1885
0
        } else {
1886
0
            rgbaPixel[0] = srcPixel[info->offsetBytesR] / info->maxChannelF;
1887
0
            rgbaPixel[1] = srcPixel[info->offsetBytesG] / info->maxChannelF;
1888
0
            rgbaPixel[2] = srcPixel[info->offsetBytesB] / info->maxChannelF;
1889
0
            rgbaPixel[3] = avifRGBFormatHasAlpha(src->format) ? (srcPixel[info->offsetBytesA] / info->maxChannelF) : 1.0f;
1890
0
        }
1891
0
    }
1892
0
}
1893
1894
void avifSetRGBAPixel(const avifRGBImage * dst, uint32_t x, uint32_t y, const avifRGBColorSpaceInfo * info, const float rgbaPixel[4])
1895
0
{
1896
0
    assert(dst != NULL);
1897
0
    assert(!dst->isFloat || dst->depth == 16);
1898
0
    assert(dst->format != AVIF_RGB_FORMAT_RGB_565 || dst->depth == 8);
1899
0
    assert(rgbaPixel[0] >= 0.0f && rgbaPixel[0] <= 1.0f);
1900
0
    assert(rgbaPixel[1] >= 0.0f && rgbaPixel[1] <= 1.0f);
1901
0
    assert(rgbaPixel[2] >= 0.0f && rgbaPixel[2] <= 1.0f);
1902
1903
0
    uint8_t * const dstPixel = &dst->pixels[(size_t)y * dst->rowBytes + (size_t)x * info->pixelBytes];
1904
1905
0
    uint8_t * const ptrR = &dstPixel[info->offsetBytesR];
1906
0
    uint8_t * const ptrG = &dstPixel[info->offsetBytesG];
1907
0
    uint8_t * const ptrB = &dstPixel[info->offsetBytesB];
1908
0
    uint8_t * const ptrA = avifRGBFormatHasAlpha(dst->format) ? &dstPixel[info->offsetBytesA] : NULL;
1909
0
    if (dst->depth > 8) {
1910
0
        if (dst->isFloat) {
1911
0
            *((uint16_t *)ptrR) = avifFloatToF16(rgbaPixel[0]);
1912
0
            *((uint16_t *)ptrG) = avifFloatToF16(rgbaPixel[1]);
1913
0
            *((uint16_t *)ptrB) = avifFloatToF16(rgbaPixel[2]);
1914
0
            if (ptrA) {
1915
0
                *((uint16_t *)ptrA) = avifFloatToF16(rgbaPixel[3]);
1916
0
            }
1917
0
        } else {
1918
0
            *((uint16_t *)ptrR) = (uint16_t)(0.5f + (rgbaPixel[0] * info->maxChannelF));
1919
0
            *((uint16_t *)ptrG) = (uint16_t)(0.5f + (rgbaPixel[1] * info->maxChannelF));
1920
0
            *((uint16_t *)ptrB) = (uint16_t)(0.5f + (rgbaPixel[2] * info->maxChannelF));
1921
0
            if (ptrA) {
1922
0
                *((uint16_t *)ptrA) = (uint16_t)(0.5f + (rgbaPixel[3] * info->maxChannelF));
1923
0
            }
1924
0
        }
1925
0
    } else {
1926
0
        avifStoreRGB8Pixel(dst->format,
1927
0
                           (uint8_t)(0.5f + (rgbaPixel[0] * info->maxChannelF)),
1928
0
                           (uint8_t)(0.5f + (rgbaPixel[1] * info->maxChannelF)),
1929
0
                           (uint8_t)(0.5f + (rgbaPixel[2] * info->maxChannelF)),
1930
0
                           ptrR,
1931
0
                           ptrG,
1932
0
                           ptrB);
1933
0
        if (ptrA) {
1934
0
            *ptrA = (uint8_t)(0.5f + (rgbaPixel[3] * info->maxChannelF));
1935
0
        }
1936
0
    }
1937
0
}