Coverage Report

Created: 2025-12-31 07:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebp/src/enc/picture_csp_enc.c
Line
Count
Source
1
// Copyright 2014 Google Inc. All Rights Reserved.
2
//
3
// Use of this source code is governed by a BSD-style license
4
// that can be found in the COPYING file in the root of the source
5
// tree. An additional intellectual property rights grant can be found
6
// in the file PATENTS. All contributing project authors may
7
// be found in the AUTHORS file in the root of the source tree.
8
// -----------------------------------------------------------------------------
9
//
10
// WebPPicture utils for colorspace conversion
11
//
12
// Author: Skal (pascal.massimino@gmail.com)
13
14
#include <assert.h>
15
#include <math.h>
16
#include <stdlib.h>
17
#include <string.h>
18
19
#include "sharpyuv/sharpyuv.h"
20
#include "sharpyuv/sharpyuv_csp.h"
21
#include "src/dsp/cpu.h"
22
#include "src/dsp/dsp.h"
23
#include "src/dsp/lossless.h"
24
#include "src/dsp/yuv.h"
25
#include "src/enc/vp8i_enc.h"
26
#include "src/utils/random_utils.h"
27
#include "src/utils/utils.h"
28
#include "src/webp/encode.h"
29
#include "src/webp/types.h"
30
31
#if defined(WEBP_USE_THREAD) && !defined(_WIN32)
32
#include <pthread.h>
33
#endif
34
35
1.67k
#define ALPHA_OFFSET CHANNEL_OFFSET(0)
36
37
//------------------------------------------------------------------------------
38
// Detection of non-trivial transparency
39
40
// Returns true if alpha[] has non-0xff values.
41
static int CheckNonOpaque(const uint8_t* alpha, int width, int height,
42
6.32k
                          int x_step, int y_step) {
43
6.32k
  if (alpha == NULL) return 0;
44
3.79k
  WebPInitAlphaProcessing();
45
3.79k
  if (x_step == 1) {
46
2.17k
    for (; height-- > 0; alpha += y_step) {
47
2.17k
      if (WebPHasAlpha8b(alpha, width)) return 1;
48
2.17k
    }
49
2.17k
  } else {
50
143k
    for (; height-- > 0; alpha += y_step) {
51
143k
      if (WebPHasAlpha32b(alpha, width)) return 1;
52
143k
    }
53
2.17k
  }
54
554
  return 0;
55
3.79k
}
56
57
// Checking for the presence of non-opaque alpha.
58
3.16k
int WebPPictureHasTransparency(const WebPPicture* picture) {
59
3.16k
  if (picture == NULL) return 0;
60
3.16k
  if (picture->use_argb) {
61
0
    if (picture->argb != NULL) {
62
0
      return CheckNonOpaque((const uint8_t*)picture->argb + ALPHA_OFFSET,
63
0
                            picture->width, picture->height, 4,
64
0
                            picture->argb_stride * sizeof(*picture->argb));
65
0
    }
66
0
    return 0;
67
0
  }
68
3.16k
  return CheckNonOpaque(picture->a, picture->width, picture->height, 1,
69
3.16k
                        picture->a_stride);
70
3.16k
}
71
72
extern VP8CPUInfo VP8GetCPUInfo;
73
74
//------------------------------------------------------------------------------
75
// Sharp RGB->YUV conversion
76
77
static const int kMinDimensionIterativeConversion = 4;
78
79
//------------------------------------------------------------------------------
80
// Main function
81
82
static int PreprocessARGB(const uint8_t* r_ptr, const uint8_t* g_ptr,
83
                          const uint8_t* b_ptr, int step, int rgb_stride,
84
0
                          WebPPicture* const picture) {
85
0
  const int ok = SharpYuvConvert(
86
0
      r_ptr, g_ptr, b_ptr, step, rgb_stride, /*rgb_bit_depth=*/8, picture->y,
87
0
      picture->y_stride, picture->u, picture->uv_stride, picture->v,
88
0
      picture->uv_stride, /*yuv_bit_depth=*/8, picture->width, picture->height,
89
0
      SharpYuvGetConversionMatrix(kSharpYuvMatrixWebp));
90
0
  if (!ok) {
91
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY);
92
0
  }
93
0
  return ok;
94
0
}
95
96
static WEBP_INLINE void ConvertRowToY(const uint8_t* const r_ptr,
97
                                      const uint8_t* const g_ptr,
98
                                      const uint8_t* const b_ptr, int step,
99
                                      uint8_t* const dst_y, int width,
100
0
                                      VP8Random* const rg) {
101
0
  int i, j;
102
0
  for (i = 0, j = 0; i < width; i += 1, j += step) {
103
0
    dst_y[i] =
104
0
        VP8RGBToY(r_ptr[j], g_ptr[j], b_ptr[j], VP8RandomBits(rg, YUV_FIX));
105
0
  }
106
0
}
107
108
static WEBP_INLINE void ConvertRowsToUV(const uint16_t* rgb,
109
                                        uint8_t* const dst_u,
110
                                        uint8_t* const dst_v, int width,
111
0
                                        VP8Random* const rg) {
112
0
  int i;
113
0
  for (i = 0; i < width; i += 1, rgb += 4) {
114
0
    const int r = rgb[0], g = rgb[1], b = rgb[2];
115
0
    dst_u[i] = VP8RGBToU(r, g, b, VP8RandomBits(rg, YUV_FIX + 2));
116
0
    dst_v[i] = VP8RGBToV(r, g, b, VP8RandomBits(rg, YUV_FIX + 2));
117
0
  }
118
0
}
119
120
extern void SharpYuvInit(VP8CPUInfo cpu_info_func);
121
122
static int ImportYUVAFromRGBA(const uint8_t* r_ptr, const uint8_t* g_ptr,
123
                              const uint8_t* b_ptr, const uint8_t* a_ptr,
124
                              int step,        // bytes per pixel
125
                              int rgb_stride,  // bytes per scanline
126
                              float dithering, int use_iterative_conversion,
127
3.16k
                              WebPPicture* const picture) {
128
3.16k
  int y;
129
3.16k
  const int width = picture->width;
130
3.16k
  const int height = picture->height;
131
3.16k
  const int has_alpha = CheckNonOpaque(a_ptr, width, height, step, rgb_stride);
132
133
3.16k
  picture->colorspace = has_alpha ? WEBP_YUV420A : WEBP_YUV420;
134
3.16k
  picture->use_argb = 0;
135
136
  // disable smart conversion if source is too small (overkill).
137
3.16k
  if (width < kMinDimensionIterativeConversion ||
138
2.77k
      height < kMinDimensionIterativeConversion) {
139
569
    use_iterative_conversion = 0;
140
569
  }
141
142
3.16k
  if (!WebPPictureAllocYUVA(picture)) {
143
0
    return 0;
144
0
  }
145
3.16k
  if (has_alpha) {
146
1.62k
    assert(step == 4);
147
1.62k
  }
148
149
3.16k
  if (use_iterative_conversion) {
150
0
    SharpYuvInit(VP8GetCPUInfo);
151
0
    if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) {
152
0
      return 0;
153
0
    }
154
0
    if (has_alpha) {
155
0
      WebPExtractAlpha(a_ptr, rgb_stride, width, height, picture->a,
156
0
                       picture->a_stride);
157
0
    }
158
3.16k
  } else {
159
3.16k
    const int uv_width = (width + 1) >> 1;
160
    // temporary storage for accumulated R/G/B values during conversion to U/V
161
3.16k
    uint16_t* const tmp_rgb =
162
3.16k
        (uint16_t*)WebPSafeMalloc(4 * uv_width, sizeof(*tmp_rgb));
163
3.16k
    uint8_t* dst_y = picture->y;
164
3.16k
    uint8_t* dst_u = picture->u;
165
3.16k
    uint8_t* dst_v = picture->v;
166
3.16k
    uint8_t* dst_a = picture->a;
167
168
3.16k
    VP8Random base_rg;
169
3.16k
    VP8Random* rg = NULL;
170
3.16k
    if (dithering > 0.) {
171
0
      VP8InitRandom(&base_rg, dithering);
172
0
      rg = &base_rg;
173
0
    }
174
3.16k
    WebPInitConvertARGBToYUV();
175
3.16k
    WebPInitGammaTables();
176
177
3.16k
    if (tmp_rgb == NULL) {
178
0
      return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY);
179
0
    }
180
181
3.16k
    if (rg == NULL) {
182
      // Downsample Y/U/V planes, two rows at a time
183
3.16k
      WebPImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride,
184
3.16k
                             has_alpha, width, height, tmp_rgb,
185
3.16k
                             picture->y_stride, picture->uv_stride,
186
3.16k
                             picture->a_stride, dst_y, dst_u, dst_v, dst_a);
187
3.16k
      if (height & 1) {
188
1.16k
        dst_y += (height - 1) * (ptrdiff_t)picture->y_stride;
189
1.16k
        dst_u += (height >> 1) * (ptrdiff_t)picture->uv_stride;
190
1.16k
        dst_v += (height >> 1) * (ptrdiff_t)picture->uv_stride;
191
1.16k
        r_ptr += (height - 1) * (ptrdiff_t)rgb_stride;
192
1.16k
        b_ptr += (height - 1) * (ptrdiff_t)rgb_stride;
193
1.16k
        g_ptr += (height - 1) * (ptrdiff_t)rgb_stride;
194
1.16k
        if (has_alpha) {
195
862
          dst_a += (height - 1) * (ptrdiff_t)picture->a_stride;
196
862
          a_ptr += (height - 1) * (ptrdiff_t)rgb_stride;
197
862
        }
198
1.16k
        WebPImportYUVAFromRGBALastLine(r_ptr, g_ptr, b_ptr, a_ptr, step,
199
1.16k
                                       has_alpha, width, tmp_rgb, dst_y, dst_u,
200
1.16k
                                       dst_v, dst_a);
201
1.16k
      }
202
3.16k
    } else {
203
      // Copy of WebPImportYUVAFromRGBA/WebPImportYUVAFromRGBALastLine,
204
      // but with dithering.
205
0
      for (y = 0; y < (height >> 1); ++y) {
206
0
        int rows_have_alpha = has_alpha;
207
0
        ConvertRowToY(r_ptr, g_ptr, b_ptr, step, dst_y, width, rg);
208
0
        ConvertRowToY(r_ptr + rgb_stride, g_ptr + rgb_stride,
209
0
                      b_ptr + rgb_stride, step, dst_y + picture->y_stride,
210
0
                      width, rg);
211
0
        dst_y += 2 * picture->y_stride;
212
0
        if (has_alpha) {
213
0
          rows_have_alpha &= !WebPExtractAlpha(a_ptr, rgb_stride, width, 2,
214
0
                                               dst_a, picture->a_stride);
215
0
          dst_a += 2 * picture->a_stride;
216
0
        }
217
        // Collect averaged R/G/B(/A)
218
0
        if (!rows_have_alpha) {
219
0
          WebPAccumulateRGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, tmp_rgb,
220
0
                            width);
221
0
        } else {
222
0
          WebPAccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, rgb_stride, tmp_rgb,
223
0
                             width);
224
0
        }
225
        // Convert to U/V
226
0
        ConvertRowsToUV(tmp_rgb, dst_u, dst_v, uv_width, rg);
227
0
        dst_u += picture->uv_stride;
228
0
        dst_v += picture->uv_stride;
229
0
        r_ptr += 2 * rgb_stride;
230
0
        b_ptr += 2 * rgb_stride;
231
0
        g_ptr += 2 * rgb_stride;
232
0
        if (has_alpha) a_ptr += 2 * rgb_stride;
233
0
      }
234
0
      if (height & 1) {  // extra last row
235
0
        int row_has_alpha = has_alpha;
236
0
        ConvertRowToY(r_ptr, g_ptr, b_ptr, step, dst_y, width, rg);
237
0
        if (row_has_alpha) {
238
0
          row_has_alpha &= !WebPExtractAlpha(a_ptr, 0, width, 1, dst_a, 0);
239
0
        }
240
        // Collect averaged R/G/B(/A)
241
0
        if (!row_has_alpha) {
242
          // Collect averaged R/G/B
243
0
          WebPAccumulateRGB(r_ptr, g_ptr, b_ptr, step, /*rgb_stride=*/0,
244
0
                            tmp_rgb, width);
245
0
        } else {
246
0
          WebPAccumulateRGBA(r_ptr, g_ptr, b_ptr, a_ptr, /*rgb_stride=*/0,
247
0
                             tmp_rgb, width);
248
0
        }
249
0
        ConvertRowsToUV(tmp_rgb, dst_u, dst_v, uv_width, rg);
250
0
      }
251
0
    }
252
253
3.16k
    WebPSafeFree(tmp_rgb);
254
3.16k
  }
255
3.16k
  return 1;
256
3.16k
}
257
258
#undef SUM4
259
#undef SUM2
260
#undef SUM4ALPHA
261
#undef SUM2ALPHA
262
263
//------------------------------------------------------------------------------
264
// call for ARGB->YUVA conversion
265
266
static int PictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace,
267
2.14k
                             float dithering, int use_iterative_conversion) {
268
2.14k
  if (picture == NULL) return 0;
269
2.14k
  if (picture->argb == NULL) {
270
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
271
2.14k
  } else if ((colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) {
272
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION);
273
2.14k
  } else {
274
2.14k
    const uint8_t* const argb = (const uint8_t*)picture->argb;
275
2.14k
    const uint8_t* const a = argb + CHANNEL_OFFSET(0);
276
2.14k
    const uint8_t* const r = argb + CHANNEL_OFFSET(1);
277
2.14k
    const uint8_t* const g = argb + CHANNEL_OFFSET(2);
278
2.14k
    const uint8_t* const b = argb + CHANNEL_OFFSET(3);
279
280
2.14k
    picture->colorspace = WEBP_YUV420;
281
2.14k
    return ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride,
282
2.14k
                              dithering, use_iterative_conversion, picture);
283
2.14k
  }
284
2.14k
}
285
286
int WebPPictureARGBToYUVADithered(WebPPicture* picture, WebPEncCSP colorspace,
287
2.14k
                                  float dithering) {
288
2.14k
  return PictureARGBToYUVA(picture, colorspace, dithering, 0);
289
2.14k
}
290
291
0
int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) {
292
0
  return PictureARGBToYUVA(picture, colorspace, 0.f, 0);
293
0
}
294
295
0
int WebPPictureSharpARGBToYUVA(WebPPicture* picture) {
296
0
  return PictureARGBToYUVA(picture, WEBP_YUV420, 0.f, 1);
297
0
}
298
// for backward compatibility
299
0
int WebPPictureSmartARGBToYUVA(WebPPicture* picture) {
300
0
  return WebPPictureSharpARGBToYUVA(picture);
301
0
}
302
303
//------------------------------------------------------------------------------
304
// call for YUVA -> ARGB conversion
305
306
0
int WebPPictureYUVAToARGB(WebPPicture* picture) {
307
0
  if (picture == NULL) return 0;
308
0
  if (picture->y == NULL || picture->u == NULL || picture->v == NULL) {
309
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
310
0
  }
311
0
  if ((picture->colorspace & WEBP_CSP_ALPHA_BIT) && picture->a == NULL) {
312
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
313
0
  }
314
0
  if ((picture->colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) {
315
0
    return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION);
316
0
  }
317
  // Allocate a new argb buffer (discarding the previous one).
318
0
  if (!WebPPictureAllocARGB(picture)) return 0;
319
0
  picture->use_argb = 1;
320
321
  // Convert
322
0
  {
323
0
    int y;
324
0
    const int width = picture->width;
325
0
    const int height = picture->height;
326
0
    const int argb_stride = 4 * picture->argb_stride;
327
0
    uint8_t* dst = (uint8_t*)picture->argb;
328
0
    const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y;
329
0
    WebPUpsampleLinePairFunc upsample =
330
0
        WebPGetLinePairConverter(ALPHA_OFFSET > 0);
331
332
    // First row, with replicated top samples.
333
0
    upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width);
334
0
    cur_y += picture->y_stride;
335
0
    dst += argb_stride;
336
    // Center rows.
337
0
    for (y = 1; y + 1 < height; y += 2) {
338
0
      const uint8_t* const top_u = cur_u;
339
0
      const uint8_t* const top_v = cur_v;
340
0
      cur_u += picture->uv_stride;
341
0
      cur_v += picture->uv_stride;
342
0
      upsample(cur_y, cur_y + picture->y_stride, top_u, top_v, cur_u, cur_v,
343
0
               dst, dst + argb_stride, width);
344
0
      cur_y += 2 * picture->y_stride;
345
0
      dst += 2 * argb_stride;
346
0
    }
347
    // Last row (if needed), with replicated bottom samples.
348
0
    if (height > 1 && !(height & 1)) {
349
0
      upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width);
350
0
    }
351
    // Insert alpha values if needed, in replacement for the default 0xff ones.
352
0
    if (picture->colorspace & WEBP_CSP_ALPHA_BIT) {
353
0
      for (y = 0; y < height; ++y) {
354
0
        uint32_t* const argb_dst = picture->argb + y * picture->argb_stride;
355
0
        const uint8_t* const src = picture->a + y * picture->a_stride;
356
0
        int x;
357
0
        for (x = 0; x < width; ++x) {
358
0
          argb_dst[x] = (argb_dst[x] & 0x00ffffffu) | ((uint32_t)src[x] << 24);
359
0
        }
360
0
      }
361
0
    }
362
0
  }
363
0
  return 1;
364
0
}
365
366
//------------------------------------------------------------------------------
367
// automatic import / conversion
368
369
static int Import(WebPPicture* const picture, const uint8_t* rgb,
370
3.16k
                  int rgb_stride, int step, int swap_rb, int import_alpha) {
371
3.16k
  int y;
372
  // swap_rb -> b,g,r,a , !swap_rb -> r,g,b,a
373
3.16k
  const uint8_t* r_ptr = rgb + (swap_rb ? 2 : 0);
374
3.16k
  const uint8_t* g_ptr = rgb + 1;
375
3.16k
  const uint8_t* b_ptr = rgb + (swap_rb ? 0 : 2);
376
3.16k
  const int width = picture->width;
377
3.16k
  const int height = picture->height;
378
379
3.16k
  if (abs(rgb_stride) < (import_alpha ? 4 : 3) * width) return 0;
380
381
3.16k
  if (!picture->use_argb) {
382
1.01k
    const uint8_t* a_ptr = import_alpha ? rgb + 3 : NULL;
383
1.01k
    return ImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride,
384
1.01k
                              0.f /* no dithering */, 0, picture);
385
1.01k
  }
386
2.14k
  if (!WebPPictureAlloc(picture)) return 0;
387
388
2.14k
  VP8LDspInit();
389
2.14k
  WebPInitAlphaProcessing();
390
391
2.14k
  if (import_alpha) {
392
    // dst[] byte order is {a,r,g,b} for big-endian, {b,g,r,a} for little endian
393
1.67k
    uint32_t* dst = picture->argb;
394
1.67k
    const int do_copy = (ALPHA_OFFSET == 3) && swap_rb;
395
1.67k
    assert(step == 4);
396
1.67k
    if (do_copy) {
397
0
      for (y = 0; y < height; ++y) {
398
0
        memcpy(dst, rgb, width * 4);
399
0
        rgb += rgb_stride;
400
0
        dst += picture->argb_stride;
401
0
      }
402
1.67k
    } else {
403
1.30M
      for (y = 0; y < height; ++y) {
404
#ifdef WORDS_BIGENDIAN
405
        // BGRA or RGBA input order.
406
        const uint8_t* a_ptr = rgb + 3;
407
        WebPPackARGB(a_ptr, r_ptr, g_ptr, b_ptr, width, dst);
408
        r_ptr += rgb_stride;
409
        g_ptr += rgb_stride;
410
        b_ptr += rgb_stride;
411
#else
412
        // RGBA input order. Need to swap R and B.
413
1.30M
        VP8LConvertBGRAToRGBA((const uint32_t*)rgb, width, (uint8_t*)dst);
414
1.30M
#endif
415
1.30M
        rgb += rgb_stride;
416
1.30M
        dst += picture->argb_stride;
417
1.30M
      }
418
1.67k
    }
419
1.67k
  } else {
420
472
    uint32_t* dst = picture->argb;
421
472
    assert(step >= 3);
422
87.7k
    for (y = 0; y < height; ++y) {
423
87.2k
      WebPPackRGB(r_ptr, g_ptr, b_ptr, width, step, dst);
424
87.2k
      r_ptr += rgb_stride;
425
87.2k
      g_ptr += rgb_stride;
426
87.2k
      b_ptr += rgb_stride;
427
87.2k
      dst += picture->argb_stride;
428
87.2k
    }
429
472
  }
430
2.14k
  return 1;
431
2.14k
}
432
433
// Public API
434
435
#if !defined(WEBP_REDUCE_CSP)
436
437
int WebPPictureImportBGR(WebPPicture* picture, const uint8_t* bgr,
438
0
                         int bgr_stride) {
439
0
  return (picture != NULL && bgr != NULL)
440
0
             ? Import(picture, bgr, bgr_stride, 3, 1, 0)
441
0
             : 0;
442
0
}
443
444
int WebPPictureImportBGRA(WebPPicture* picture, const uint8_t* bgra,
445
0
                          int bgra_stride) {
446
0
  return (picture != NULL && bgra != NULL)
447
0
             ? Import(picture, bgra, bgra_stride, 4, 1, 1)
448
0
             : 0;
449
0
}
450
451
int WebPPictureImportBGRX(WebPPicture* picture, const uint8_t* bgrx,
452
0
                          int bgrx_stride) {
453
0
  return (picture != NULL && bgrx != NULL)
454
0
             ? Import(picture, bgrx, bgrx_stride, 4, 1, 0)
455
0
             : 0;
456
0
}
457
458
#endif  // WEBP_REDUCE_CSP
459
460
int WebPPictureImportRGB(WebPPicture* picture, const uint8_t* rgb,
461
1.45k
                         int rgb_stride) {
462
1.45k
  return (picture != NULL && rgb != NULL)
463
1.45k
             ? Import(picture, rgb, rgb_stride, 3, 0, 0)
464
1.45k
             : 0;
465
1.45k
}
466
467
int WebPPictureImportRGBA(WebPPicture* picture, const uint8_t* rgba,
468
1.70k
                          int rgba_stride) {
469
1.70k
  return (picture != NULL && rgba != NULL)
470
1.70k
             ? Import(picture, rgba, rgba_stride, 4, 0, 1)
471
1.70k
             : 0;
472
1.70k
}
473
474
int WebPPictureImportRGBX(WebPPicture* picture, const uint8_t* rgbx,
475
0
                          int rgbx_stride) {
476
0
  return (picture != NULL && rgbx != NULL)
477
0
             ? Import(picture, rgbx, rgbx_stride, 4, 0, 0)
478
0
             : 0;
479
0
}
480
481
//------------------------------------------------------------------------------