picture.c revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright 2011 Google Inc. All Rights Reserved.
2//
3// This code is licensed under the same terms as WebM:
4//  Software License Agreement:  http://www.webmproject.org/license/software/
5//  Additional IP Rights Grant:  http://www.webmproject.org/license/additional/
6// -----------------------------------------------------------------------------
7//
8// WebPPicture utils: colorspace conversion, crop, ...
9//
10// Author: Skal (pascal.massimino@gmail.com)
11
12#include <assert.h>
13#include <stdlib.h>
14#include <math.h>
15
16#include "./vp8enci.h"
17#include "../utils/rescaler.h"
18#include "../utils/utils.h"
19#include "../dsp/dsp.h"
20#include "../dsp/yuv.h"
21
22#if defined(__cplusplus) || defined(c_plusplus)
23extern "C" {
24#endif
25
26#define HALVE(x) (((x) + 1) >> 1)
27#define IS_YUV_CSP(csp, YUV_CSP) (((csp) & WEBP_CSP_UV_MASK) == (YUV_CSP))
28
29static const union {
30  uint32_t argb;
31  uint8_t  bytes[4];
32} test_endian = { 0xff000000u };
33#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff)
34
35//------------------------------------------------------------------------------
36// WebPPicture
37//------------------------------------------------------------------------------
38
39int WebPPictureAlloc(WebPPicture* picture) {
40  if (picture != NULL) {
41    const WebPEncCSP uv_csp = picture->colorspace & WEBP_CSP_UV_MASK;
42    const int has_alpha = picture->colorspace & WEBP_CSP_ALPHA_BIT;
43    const int width = picture->width;
44    const int height = picture->height;
45
46    if (!picture->use_argb) {
47      const int y_stride = width;
48      const int uv_width = HALVE(width);
49      const int uv_height = HALVE(height);
50      const int uv_stride = uv_width;
51      int uv0_stride = 0;
52      int a_width, a_stride;
53      uint64_t y_size, uv_size, uv0_size, a_size, total_size;
54      uint8_t* mem;
55
56      // U/V
57      switch (uv_csp) {
58        case WEBP_YUV420:
59          break;
60#ifdef WEBP_EXPERIMENTAL_FEATURES
61        case WEBP_YUV400:    // for now, we'll just reset the U/V samples
62          break;
63        case WEBP_YUV422:
64          uv0_stride = uv_width;
65          break;
66        case WEBP_YUV444:
67          uv0_stride = width;
68          break;
69#endif
70        default:
71          return 0;
72      }
73      uv0_size = height * uv0_stride;
74
75      // alpha
76      a_width = has_alpha ? width : 0;
77      a_stride = a_width;
78      y_size = (uint64_t)y_stride * height;
79      uv_size = (uint64_t)uv_stride * uv_height;
80      a_size =  (uint64_t)a_stride * height;
81
82      total_size = y_size + a_size + 2 * uv_size + 2 * uv0_size;
83
84      // Security and validation checks
85      if (width <= 0 || height <= 0 ||         // luma/alpha param error
86          uv_width < 0 || uv_height < 0) {     // u/v param error
87        return 0;
88      }
89      // Clear previous buffer and allocate a new one.
90      WebPPictureFree(picture);   // erase previous buffer
91      mem = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*mem));
92      if (mem == NULL) return 0;
93
94      // From now on, we're in the clear, we can no longer fail...
95      picture->memory_ = (void*)mem;
96      picture->y_stride  = y_stride;
97      picture->uv_stride = uv_stride;
98      picture->a_stride  = a_stride;
99      picture->uv0_stride = uv0_stride;
100      // TODO(skal): we could align the y/u/v planes and adjust stride.
101      picture->y = mem;
102      mem += y_size;
103
104      picture->u = mem;
105      mem += uv_size;
106      picture->v = mem;
107      mem += uv_size;
108
109      if (a_size) {
110        picture->a = mem;
111        mem += a_size;
112      }
113      if (uv0_size) {
114        picture->u0 = mem;
115        mem += uv0_size;
116        picture->v0 = mem;
117        mem += uv0_size;
118      }
119    } else {
120      void* memory;
121      const uint64_t argb_size = (uint64_t)width * height;
122      if (width <= 0 || height <= 0) {
123        return 0;
124      }
125      // Clear previous buffer and allocate a new one.
126      WebPPictureFree(picture);   // erase previous buffer
127      memory = WebPSafeMalloc(argb_size, sizeof(*picture->argb));
128      if (memory == NULL) return 0;
129
130      // TODO(skal): align plane to cache line?
131      picture->memory_argb_ = memory;
132      picture->argb = (uint32_t*)memory;
133      picture->argb_stride = width;
134    }
135  }
136  return 1;
137}
138
139// Remove reference to the ARGB buffer (doesn't free anything).
140static void PictureResetARGB(WebPPicture* const picture) {
141  picture->memory_argb_ = NULL;
142  picture->argb = NULL;
143  picture->argb_stride = 0;
144}
145
146// Remove reference to the YUVA buffer (doesn't free anything).
147static void PictureResetYUVA(WebPPicture* const picture) {
148  picture->memory_ = NULL;
149  picture->y = picture->u = picture->v = picture->a = NULL;
150  picture->u0 = picture->v0 = NULL;
151  picture->y_stride = picture->uv_stride = 0;
152  picture->a_stride = 0;
153  picture->uv0_stride = 0;
154}
155
156// Grab the 'specs' (writer, *opaque, width, height...) from 'src' and copy them
157// into 'dst'. Mark 'dst' as not owning any memory.
158static void WebPPictureGrabSpecs(const WebPPicture* const src,
159                                 WebPPicture* const dst) {
160  assert(src != NULL && dst != NULL);
161  *dst = *src;
162  PictureResetYUVA(dst);
163  PictureResetARGB(dst);
164}
165
166// Allocate a new argb buffer, discarding any existing one and preserving
167// the other YUV(A) buffer.
168static int PictureAllocARGB(WebPPicture* const picture) {
169  WebPPicture tmp;
170  free(picture->memory_argb_);
171  PictureResetARGB(picture);
172  picture->use_argb = 1;
173  WebPPictureGrabSpecs(picture, &tmp);
174  if (!WebPPictureAlloc(&tmp)) {
175    return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY);
176  }
177  picture->memory_argb_ = tmp.memory_argb_;
178  picture->argb = tmp.argb;
179  picture->argb_stride = tmp.argb_stride;
180  return 1;
181}
182
183// Release memory owned by 'picture' (both YUV and ARGB buffers).
184void WebPPictureFree(WebPPicture* picture) {
185  if (picture != NULL) {
186    free(picture->memory_);
187    free(picture->memory_argb_);
188    PictureResetYUVA(picture);
189    PictureResetARGB(picture);
190  }
191}
192
193//------------------------------------------------------------------------------
194// Picture copying
195
196// Not worth moving to dsp/enc.c (only used here).
197static void CopyPlane(const uint8_t* src, int src_stride,
198                      uint8_t* dst, int dst_stride, int width, int height) {
199  while (height-- > 0) {
200    memcpy(dst, src, width);
201    src += src_stride;
202    dst += dst_stride;
203  }
204}
205
206// Adjust top-left corner to chroma sample position.
207static void SnapTopLeftPosition(const WebPPicture* const pic,
208                                int* const left, int* const top) {
209  if (!pic->use_argb) {
210    const int is_yuv422 = IS_YUV_CSP(pic->colorspace, WEBP_YUV422);
211    if (IS_YUV_CSP(pic->colorspace, WEBP_YUV420) || is_yuv422) {
212      *left &= ~1;
213      if (!is_yuv422) *top &= ~1;
214    }
215  }
216}
217
218// Adjust top-left corner and verify that the sub-rectangle is valid.
219static int AdjustAndCheckRectangle(const WebPPicture* const pic,
220                                   int* const left, int* const top,
221                                   int width, int height) {
222  SnapTopLeftPosition(pic, left, top);
223  if ((*left) < 0 || (*top) < 0) return 0;
224  if (width <= 0 || height <= 0) return 0;
225  if ((*left) + width > pic->width) return 0;
226  if ((*top) + height > pic->height) return 0;
227  return 1;
228}
229
230int WebPPictureCopy(const WebPPicture* src, WebPPicture* dst) {
231  if (src == NULL || dst == NULL) return 0;
232  if (src == dst) return 1;
233
234  WebPPictureGrabSpecs(src, dst);
235  if (!WebPPictureAlloc(dst)) return 0;
236
237  if (!src->use_argb) {
238    CopyPlane(src->y, src->y_stride,
239              dst->y, dst->y_stride, dst->width, dst->height);
240    CopyPlane(src->u, src->uv_stride,
241              dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
242    CopyPlane(src->v, src->uv_stride,
243              dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
244    if (dst->a != NULL)  {
245      CopyPlane(src->a, src->a_stride,
246                dst->a, dst->a_stride, dst->width, dst->height);
247    }
248#ifdef WEBP_EXPERIMENTAL_FEATURES
249    if (dst->u0 != NULL)  {
250      int uv0_width = src->width;
251      if (IS_YUV_CSP(dst->colorspace, WEBP_YUV422)) {
252        uv0_width = HALVE(uv0_width);
253      }
254      CopyPlane(src->u0, src->uv0_stride,
255                dst->u0, dst->uv0_stride, uv0_width, dst->height);
256      CopyPlane(src->v0, src->uv0_stride,
257                dst->v0, dst->uv0_stride, uv0_width, dst->height);
258    }
259#endif
260  } else {
261    CopyPlane((const uint8_t*)src->argb, 4 * src->argb_stride,
262              (uint8_t*)dst->argb, 4 * dst->argb_stride,
263              4 * dst->width, dst->height);
264  }
265  return 1;
266}
267
268int WebPPictureIsView(const WebPPicture* picture) {
269  if (picture == NULL) return 0;
270  if (picture->use_argb) {
271    return (picture->memory_argb_ == NULL);
272  }
273  return (picture->memory_ == NULL);
274}
275
276int WebPPictureView(const WebPPicture* src,
277                    int left, int top, int width, int height,
278                    WebPPicture* dst) {
279  if (src == NULL || dst == NULL) return 0;
280
281  // verify rectangle position.
282  if (!AdjustAndCheckRectangle(src, &left, &top, width, height)) return 0;
283
284  if (src != dst) {  // beware of aliasing! We don't want to leak 'memory_'.
285    WebPPictureGrabSpecs(src, dst);
286  }
287  dst->width = width;
288  dst->height = height;
289  if (!src->use_argb) {
290    dst->y = src->y + top * src->y_stride + left;
291    dst->u = src->u + (top >> 1) * src->uv_stride + (left >> 1);
292    dst->v = src->v + (top >> 1) * src->uv_stride + (left >> 1);
293    if (src->a != NULL) {
294      dst->a = src->a + top * src->a_stride + left;
295    }
296#ifdef WEBP_EXPERIMENTAL_FEATURES
297    if (src->u0 != NULL) {
298      const int left_pos =
299          IS_YUV_CSP(dst->colorspace, WEBP_YUV422) ? (left >> 1) : left;
300      dst->u0 = src->u0 + top * src->uv0_stride + left_pos;
301      dst->v0 = src->v0 + top * src->uv0_stride + left_pos;
302    }
303#endif
304  } else {
305    dst->argb = src->argb + top * src->argb_stride + left;
306  }
307  return 1;
308}
309
310//------------------------------------------------------------------------------
311// Picture cropping
312
313int WebPPictureCrop(WebPPicture* pic,
314                    int left, int top, int width, int height) {
315  WebPPicture tmp;
316
317  if (pic == NULL) return 0;
318  if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0;
319
320  WebPPictureGrabSpecs(pic, &tmp);
321  tmp.width = width;
322  tmp.height = height;
323  if (!WebPPictureAlloc(&tmp)) return 0;
324
325  if (!pic->use_argb) {
326    const int y_offset = top * pic->y_stride + left;
327    const int uv_offset = (top / 2) * pic->uv_stride + left / 2;
328    CopyPlane(pic->y + y_offset, pic->y_stride,
329              tmp.y, tmp.y_stride, width, height);
330    CopyPlane(pic->u + uv_offset, pic->uv_stride,
331              tmp.u, tmp.uv_stride, HALVE(width), HALVE(height));
332    CopyPlane(pic->v + uv_offset, pic->uv_stride,
333              tmp.v, tmp.uv_stride, HALVE(width), HALVE(height));
334
335    if (tmp.a != NULL) {
336      const int a_offset = top * pic->a_stride + left;
337      CopyPlane(pic->a + a_offset, pic->a_stride,
338                tmp.a, tmp.a_stride, width, height);
339    }
340#ifdef WEBP_EXPERIMENTAL_FEATURES
341    if (tmp.u0 != NULL) {
342      int w = width;
343      int left_pos = left;
344      if (IS_YUV_CSP(tmp.colorspace, WEBP_YUV422)) {
345        w = HALVE(w);
346        left_pos = HALVE(left_pos);
347      }
348      CopyPlane(pic->u0 + top * pic->uv0_stride + left_pos, pic->uv0_stride,
349                tmp.u0, tmp.uv0_stride, w, height);
350      CopyPlane(pic->v0 + top * pic->uv0_stride + left_pos, pic->uv0_stride,
351                tmp.v0, tmp.uv0_stride, w, height);
352    }
353#endif
354  } else {
355    const uint8_t* const src =
356        (const uint8_t*)(pic->argb + top * pic->argb_stride + left);
357    CopyPlane(src, pic->argb_stride * 4,
358              (uint8_t*)tmp.argb, tmp.argb_stride * 4,
359              width * 4, height);
360  }
361  WebPPictureFree(pic);
362  *pic = tmp;
363  return 1;
364}
365
366//------------------------------------------------------------------------------
367// Simple picture rescaler
368
369static void RescalePlane(const uint8_t* src,
370                         int src_width, int src_height, int src_stride,
371                         uint8_t* dst,
372                         int dst_width, int dst_height, int dst_stride,
373                         int32_t* const work,
374                         int num_channels) {
375  WebPRescaler rescaler;
376  int y = 0;
377  WebPRescalerInit(&rescaler, src_width, src_height,
378                   dst, dst_width, dst_height, dst_stride,
379                   num_channels,
380                   src_width, dst_width,
381                   src_height, dst_height,
382                   work);
383  memset(work, 0, 2 * dst_width * num_channels * sizeof(*work));
384  while (y < src_height) {
385    y += WebPRescalerImport(&rescaler, src_height - y,
386                            src + y * src_stride, src_stride);
387    WebPRescalerExport(&rescaler);
388  }
389}
390
391int WebPPictureRescale(WebPPicture* pic, int width, int height) {
392  WebPPicture tmp;
393  int prev_width, prev_height;
394  int32_t* work;
395
396  if (pic == NULL) return 0;
397  prev_width = pic->width;
398  prev_height = pic->height;
399  // if width is unspecified, scale original proportionally to height ratio.
400  if (width == 0) {
401    width = (prev_width * height + prev_height / 2) / prev_height;
402  }
403  // if height is unspecified, scale original proportionally to width ratio.
404  if (height == 0) {
405    height = (prev_height * width + prev_width / 2) / prev_width;
406  }
407  // Check if the overall dimensions still make sense.
408  if (width <= 0 || height <= 0) return 0;
409
410  WebPPictureGrabSpecs(pic, &tmp);
411  tmp.width = width;
412  tmp.height = height;
413  if (!WebPPictureAlloc(&tmp)) return 0;
414
415  if (!pic->use_argb) {
416    work = (int32_t*)WebPSafeMalloc(2ULL * width, sizeof(*work));
417    if (work == NULL) {
418      WebPPictureFree(&tmp);
419      return 0;
420    }
421
422    RescalePlane(pic->y, prev_width, prev_height, pic->y_stride,
423                 tmp.y, width, height, tmp.y_stride, work, 1);
424    RescalePlane(pic->u,
425                 HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
426                 tmp.u,
427                 HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
428    RescalePlane(pic->v,
429                 HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
430                 tmp.v,
431                 HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
432
433    if (tmp.a != NULL) {
434      RescalePlane(pic->a, prev_width, prev_height, pic->a_stride,
435                   tmp.a, width, height, tmp.a_stride, work, 1);
436    }
437#ifdef WEBP_EXPERIMENTAL_FEATURES
438    if (tmp.u0 != NULL) {
439      const int s = IS_YUV_CSP(tmp.colorspace, WEBP_YUV422) ? 2 : 1;
440      RescalePlane(
441          pic->u0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride,
442          tmp.u0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1);
443      RescalePlane(
444          pic->v0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride,
445          tmp.v0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1);
446    }
447#endif
448  } else {
449    work = (int32_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work));
450    if (work == NULL) {
451      WebPPictureFree(&tmp);
452      return 0;
453    }
454
455    RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height,
456                 pic->argb_stride * 4,
457                 (uint8_t*)tmp.argb, width, height,
458                 tmp.argb_stride * 4,
459                 work, 4);
460
461  }
462  WebPPictureFree(pic);
463  free(work);
464  *pic = tmp;
465  return 1;
466}
467
468//------------------------------------------------------------------------------
469// WebPMemoryWriter: Write-to-memory
470
471void WebPMemoryWriterInit(WebPMemoryWriter* writer) {
472  writer->mem = NULL;
473  writer->size = 0;
474  writer->max_size = 0;
475}
476
477int WebPMemoryWrite(const uint8_t* data, size_t data_size,
478                    const WebPPicture* picture) {
479  WebPMemoryWriter* const w = (WebPMemoryWriter*)picture->custom_ptr;
480  uint64_t next_size;
481  if (w == NULL) {
482    return 1;
483  }
484  next_size = (uint64_t)w->size + data_size;
485  if (next_size > w->max_size) {
486    uint8_t* new_mem;
487    uint64_t next_max_size = 2ULL * w->max_size;
488    if (next_max_size < next_size) next_max_size = next_size;
489    if (next_max_size < 8192ULL) next_max_size = 8192ULL;
490    new_mem = (uint8_t*)WebPSafeMalloc(next_max_size, 1);
491    if (new_mem == NULL) {
492      return 0;
493    }
494    if (w->size > 0) {
495      memcpy(new_mem, w->mem, w->size);
496    }
497    free(w->mem);
498    w->mem = new_mem;
499    // down-cast is ok, thanks to WebPSafeMalloc
500    w->max_size = (size_t)next_max_size;
501  }
502  if (data_size > 0) {
503    memcpy(w->mem + w->size, data, data_size);
504    w->size += data_size;
505  }
506  return 1;
507}
508
509//------------------------------------------------------------------------------
510// Detection of non-trivial transparency
511
512// Returns true if alpha[] has non-0xff values.
513static int CheckNonOpaque(const uint8_t* alpha, int width, int height,
514                          int x_step, int y_step) {
515  if (alpha == NULL) return 0;
516  while (height-- > 0) {
517    int x;
518    for (x = 0; x < width * x_step; x += x_step) {
519      if (alpha[x] != 0xff) return 1;  // TODO(skal): check 4/8 bytes at a time.
520    }
521    alpha += y_step;
522  }
523  return 0;
524}
525
526// Checking for the presence of non-opaque alpha.
527int WebPPictureHasTransparency(const WebPPicture* picture) {
528  if (picture == NULL) return 0;
529  if (!picture->use_argb) {
530    return CheckNonOpaque(picture->a, picture->width, picture->height,
531                          1, picture->a_stride);
532  } else {
533    int x, y;
534    const uint32_t* argb = picture->argb;
535    if (argb == NULL) return 0;
536    for (y = 0; y < picture->height; ++y) {
537      for (x = 0; x < picture->width; ++x) {
538        if (argb[x] < 0xff000000u) return 1;   // test any alpha values != 0xff
539      }
540      argb += picture->argb_stride;
541    }
542  }
543  return 0;
544}
545
546//------------------------------------------------------------------------------
547// RGB -> YUV conversion
548
549// TODO: we can do better than simply 2x2 averaging on U/V samples.
550#define SUM4(ptr) ((ptr)[0] + (ptr)[step] + \
551                   (ptr)[rgb_stride] + (ptr)[rgb_stride + step])
552#define SUM2H(ptr) (2 * (ptr)[0] + 2 * (ptr)[step])
553#define SUM2V(ptr) (2 * (ptr)[0] + 2 * (ptr)[rgb_stride])
554#define SUM1(ptr)  (4 * (ptr)[0])
555#define RGB_TO_UV(x, y, SUM) {                           \
556  const int src = (2 * (step * (x) + (y) * rgb_stride)); \
557  const int dst = (x) + (y) * picture->uv_stride;        \
558  const int r = SUM(r_ptr + src);                        \
559  const int g = SUM(g_ptr + src);                        \
560  const int b = SUM(b_ptr + src);                        \
561  picture->u[dst] = VP8RGBToU(r, g, b);                  \
562  picture->v[dst] = VP8RGBToV(r, g, b);                  \
563}
564
565#define RGB_TO_UV0(x_in, x_out, y, SUM) {                \
566  const int src = (step * (x_in) + (y) * rgb_stride);    \
567  const int dst = (x_out) + (y) * picture->uv0_stride;   \
568  const int r = SUM(r_ptr + src);                        \
569  const int g = SUM(g_ptr + src);                        \
570  const int b = SUM(b_ptr + src);                        \
571  picture->u0[dst] = VP8RGBToU(r, g, b);                 \
572  picture->v0[dst] = VP8RGBToV(r, g, b);                 \
573}
574
575static void MakeGray(WebPPicture* const picture) {
576  int y;
577  const int uv_width = HALVE(picture->width);
578  const int uv_height = HALVE(picture->height);
579  for (y = 0; y < uv_height; ++y) {
580    memset(picture->u + y * picture->uv_stride, 128, uv_width);
581    memset(picture->v + y * picture->uv_stride, 128, uv_width);
582  }
583}
584
585static int ImportYUVAFromRGBA(const uint8_t* const r_ptr,
586                              const uint8_t* const g_ptr,
587                              const uint8_t* const b_ptr,
588                              const uint8_t* const a_ptr,
589                              int step,         // bytes per pixel
590                              int rgb_stride,   // bytes per scanline
591                              WebPPicture* const picture) {
592  const WebPEncCSP uv_csp = picture->colorspace & WEBP_CSP_UV_MASK;
593  int x, y;
594  const int width = picture->width;
595  const int height = picture->height;
596  const int has_alpha = CheckNonOpaque(a_ptr, width, height, step, rgb_stride);
597
598  picture->colorspace = uv_csp;
599  picture->use_argb = 0;
600  if (has_alpha) {
601    picture->colorspace |= WEBP_CSP_ALPHA_BIT;
602  }
603  if (!WebPPictureAlloc(picture)) return 0;
604
605  // Import luma plane
606  for (y = 0; y < height; ++y) {
607    for (x = 0; x < width; ++x) {
608      const int offset = step * x + y * rgb_stride;
609      picture->y[x + y * picture->y_stride] =
610          VP8RGBToY(r_ptr[offset], g_ptr[offset], b_ptr[offset]);
611    }
612  }
613
614  // Downsample U/V plane
615  if (uv_csp != WEBP_YUV400) {
616    for (y = 0; y < (height >> 1); ++y) {
617      for (x = 0; x < (width >> 1); ++x) {
618        RGB_TO_UV(x, y, SUM4);
619      }
620      if (width & 1) {
621        RGB_TO_UV(x, y, SUM2V);
622      }
623    }
624    if (height & 1) {
625      for (x = 0; x < (width >> 1); ++x) {
626        RGB_TO_UV(x, y, SUM2H);
627      }
628      if (width & 1) {
629        RGB_TO_UV(x, y, SUM1);
630      }
631    }
632
633#ifdef WEBP_EXPERIMENTAL_FEATURES
634    // Store original U/V samples too
635    if (uv_csp == WEBP_YUV422) {
636      for (y = 0; y < height; ++y) {
637        for (x = 0; x < (width >> 1); ++x) {
638          RGB_TO_UV0(2 * x, x, y, SUM2H);
639        }
640        if (width & 1) {
641          RGB_TO_UV0(2 * x, x, y, SUM1);
642        }
643      }
644    } else if (uv_csp == WEBP_YUV444) {
645      for (y = 0; y < height; ++y) {
646        for (x = 0; x < width; ++x) {
647          RGB_TO_UV0(x, x, y, SUM1);
648        }
649      }
650    }
651#endif
652  } else {
653    MakeGray(picture);
654  }
655
656  if (has_alpha) {
657    assert(step >= 4);
658    for (y = 0; y < height; ++y) {
659      for (x = 0; x < width; ++x) {
660        picture->a[x + y * picture->a_stride] =
661            a_ptr[step * x + y * rgb_stride];
662      }
663    }
664  }
665  return 1;
666}
667
668static int Import(WebPPicture* const picture,
669                  const uint8_t* const rgb, int rgb_stride,
670                  int step, int swap_rb, int import_alpha) {
671  const uint8_t* const r_ptr = rgb + (swap_rb ? 2 : 0);
672  const uint8_t* const g_ptr = rgb + 1;
673  const uint8_t* const b_ptr = rgb + (swap_rb ? 0 : 2);
674  const uint8_t* const a_ptr = import_alpha ? rgb + 3 : NULL;
675  const int width = picture->width;
676  const int height = picture->height;
677
678  if (!picture->use_argb) {
679    return ImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride,
680                              picture);
681  }
682  if (import_alpha) {
683    picture->colorspace |= WEBP_CSP_ALPHA_BIT;
684  } else {
685    picture->colorspace &= ~WEBP_CSP_ALPHA_BIT;
686  }
687  if (!WebPPictureAlloc(picture)) return 0;
688
689  if (!import_alpha) {
690    int x, y;
691    for (y = 0; y < height; ++y) {
692      for (x = 0; x < width; ++x) {
693        const int offset = step * x + y * rgb_stride;
694        const uint32_t argb =
695            0xff000000u |
696            (r_ptr[offset] << 16) |
697            (g_ptr[offset] <<  8) |
698            (b_ptr[offset]);
699        picture->argb[x + y * picture->argb_stride] = argb;
700      }
701    }
702  } else {
703    int x, y;
704    assert(step >= 4);
705    for (y = 0; y < height; ++y) {
706      for (x = 0; x < width; ++x) {
707        const int offset = step * x + y * rgb_stride;
708        const uint32_t argb = (a_ptr[offset] << 24) |
709                              (r_ptr[offset] << 16) |
710                              (g_ptr[offset] <<  8) |
711                              (b_ptr[offset]);
712        picture->argb[x + y * picture->argb_stride] = argb;
713      }
714    }
715  }
716  return 1;
717}
718#undef SUM4
719#undef SUM2V
720#undef SUM2H
721#undef SUM1
722#undef RGB_TO_UV
723
724int WebPPictureImportRGB(WebPPicture* picture,
725                         const uint8_t* rgb, int rgb_stride) {
726  return Import(picture, rgb, rgb_stride, 3, 0, 0);
727}
728
729int WebPPictureImportBGR(WebPPicture* picture,
730                         const uint8_t* rgb, int rgb_stride) {
731  return Import(picture, rgb, rgb_stride, 3, 1, 0);
732}
733
734int WebPPictureImportRGBA(WebPPicture* picture,
735                          const uint8_t* rgba, int rgba_stride) {
736  return Import(picture, rgba, rgba_stride, 4, 0, 1);
737}
738
739int WebPPictureImportBGRA(WebPPicture* picture,
740                          const uint8_t* rgba, int rgba_stride) {
741  return Import(picture, rgba, rgba_stride, 4, 1, 1);
742}
743
744int WebPPictureImportRGBX(WebPPicture* picture,
745                          const uint8_t* rgba, int rgba_stride) {
746  return Import(picture, rgba, rgba_stride, 4, 0, 0);
747}
748
749int WebPPictureImportBGRX(WebPPicture* picture,
750                          const uint8_t* rgba, int rgba_stride) {
751  return Import(picture, rgba, rgba_stride, 4, 1, 0);
752}
753
754//------------------------------------------------------------------------------
755// Automatic YUV <-> ARGB conversions.
756
757int WebPPictureYUVAToARGB(WebPPicture* picture) {
758  if (picture == NULL) return 0;
759  if (picture->memory_ == NULL || picture->y == NULL ||
760      picture->u == NULL || picture->v == NULL) {
761    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
762  }
763  if ((picture->colorspace & WEBP_CSP_ALPHA_BIT) && picture->a == NULL) {
764    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
765  }
766  if ((picture->colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) {
767    return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION);
768  }
769  // Allocate a new argb buffer (discarding the previous one).
770  if (!PictureAllocARGB(picture)) return 0;
771
772  // Convert
773  {
774    int y;
775    const int width = picture->width;
776    const int height = picture->height;
777    const int argb_stride = 4 * picture->argb_stride;
778    uint8_t* dst = (uint8_t*)picture->argb;
779    const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y;
780    WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST);
781
782    // First row, with replicated top samples.
783    upsample(NULL, cur_y, cur_u, cur_v, cur_u, cur_v, NULL, dst, width);
784    cur_y += picture->y_stride;
785    dst += argb_stride;
786    // Center rows.
787    for (y = 1; y + 1 < height; y += 2) {
788      const uint8_t* const top_u = cur_u;
789      const uint8_t* const top_v = cur_v;
790      cur_u += picture->uv_stride;
791      cur_v += picture->uv_stride;
792      upsample(cur_y, cur_y + picture->y_stride, top_u, top_v, cur_u, cur_v,
793               dst, dst + argb_stride, width);
794      cur_y += 2 * picture->y_stride;
795      dst += 2 * argb_stride;
796    }
797    // Last row (if needed), with replicated bottom samples.
798    if (height > 1 && !(height & 1)) {
799      upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width);
800    }
801    // Insert alpha values if needed, in replacement for the default 0xff ones.
802    if (picture->colorspace & WEBP_CSP_ALPHA_BIT) {
803      for (y = 0; y < height; ++y) {
804        uint32_t* const dst = picture->argb + y * picture->argb_stride;
805        const uint8_t* const src = picture->a + y * picture->a_stride;
806        int x;
807        for (x = 0; x < width; ++x) {
808          dst[x] = (dst[x] & 0x00ffffffu) | (src[x] << 24);
809        }
810      }
811    }
812  }
813  return 1;
814}
815
816int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) {
817  if (picture == NULL) return 0;
818  if (picture->argb == NULL) {
819    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
820  } else {
821    const uint8_t* const argb = (const uint8_t*)picture->argb;
822    const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1;
823    const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2;
824    const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3;
825    const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0;
826    // We work on a tmp copy of 'picture', because ImportYUVAFromRGBA()
827    // would be calling WebPPictureFree(picture) otherwise.
828    WebPPicture tmp = *picture;
829    PictureResetARGB(&tmp);  // reset ARGB buffer so that it's not free()'d.
830    tmp.use_argb = 0;
831    tmp.colorspace = colorspace & WEBP_CSP_UV_MASK;
832    if (!ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, &tmp)) {
833      return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY);
834    }
835    // Copy back the YUV specs into 'picture'.
836    tmp.argb = picture->argb;
837    tmp.argb_stride = picture->argb_stride;
838    tmp.memory_argb_ = picture->memory_argb_;
839    *picture = tmp;
840  }
841  return 1;
842}
843
844//------------------------------------------------------------------------------
845// Helper: clean up fully transparent area to help compressibility.
846
847#define SIZE 8
848#define SIZE2 (SIZE / 2)
849static int is_transparent_area(const uint8_t* ptr, int stride, int size) {
850  int y, x;
851  for (y = 0; y < size; ++y) {
852    for (x = 0; x < size; ++x) {
853      if (ptr[x]) {
854        return 0;
855      }
856    }
857    ptr += stride;
858  }
859  return 1;
860}
861
862static WEBP_INLINE void flatten(uint8_t* ptr, int v, int stride, int size) {
863  int y;
864  for (y = 0; y < size; ++y) {
865    memset(ptr, v, size);
866    ptr += stride;
867  }
868}
869
870void WebPCleanupTransparentArea(WebPPicture* pic) {
871  int x, y, w, h;
872  const uint8_t* a_ptr;
873  int values[3] = { 0 };
874
875  if (pic == NULL) return;
876
877  a_ptr = pic->a;
878  if (a_ptr == NULL) return;    // nothing to do
879
880  w = pic->width / SIZE;
881  h = pic->height / SIZE;
882  for (y = 0; y < h; ++y) {
883    int need_reset = 1;
884    for (x = 0; x < w; ++x) {
885      const int off_a = (y * pic->a_stride + x) * SIZE;
886      const int off_y = (y * pic->y_stride + x) * SIZE;
887      const int off_uv = (y * pic->uv_stride + x) * SIZE2;
888      if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) {
889        if (need_reset) {
890          values[0] = pic->y[off_y];
891          values[1] = pic->u[off_uv];
892          values[2] = pic->v[off_uv];
893          need_reset = 0;
894        }
895        flatten(pic->y + off_y, values[0], pic->y_stride, SIZE);
896        flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2);
897        flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2);
898      } else {
899        need_reset = 1;
900      }
901    }
902    // ignore the left-overs on right/bottom
903  }
904}
905
906#undef SIZE
907#undef SIZE2
908
909
910//------------------------------------------------------------------------------
911// Distortion
912
913// Max value returned in case of exact similarity.
914static const double kMinDistortion_dB = 99.;
915
916int WebPPictureDistortion(const WebPPicture* pic1, const WebPPicture* pic2,
917                          int type, float result[5]) {
918  int c;
919  DistoStats stats[5];
920  int has_alpha;
921
922  if (pic1 == NULL || pic2 == NULL ||
923      pic1->width != pic2->width || pic1->height != pic2->height ||
924      pic1->y == NULL || pic2->y == NULL ||
925      pic1->u == NULL || pic2->u == NULL ||
926      pic1->v == NULL || pic2->v == NULL ||
927      result == NULL) {
928    return 0;
929  }
930  // TODO(skal): provide distortion for ARGB too.
931  if (pic1->use_argb == 1 || pic1->use_argb != pic2->use_argb) {
932    return 0;
933  }
934
935  has_alpha = !!(pic1->colorspace & WEBP_CSP_ALPHA_BIT);
936  if (has_alpha != !!(pic2->colorspace & WEBP_CSP_ALPHA_BIT) ||
937      (has_alpha && (pic1->a == NULL || pic2->a == NULL))) {
938    return 0;
939  }
940
941  memset(stats, 0, sizeof(stats));
942  VP8SSIMAccumulatePlane(pic1->y, pic1->y_stride,
943                         pic2->y, pic2->y_stride,
944                         pic1->width, pic1->height, &stats[0]);
945  VP8SSIMAccumulatePlane(pic1->u, pic1->uv_stride,
946                         pic2->u, pic2->uv_stride,
947                         (pic1->width + 1) >> 1, (pic1->height + 1) >> 1,
948                         &stats[1]);
949  VP8SSIMAccumulatePlane(pic1->v, pic1->uv_stride,
950                         pic2->v, pic2->uv_stride,
951                         (pic1->width + 1) >> 1, (pic1->height + 1) >> 1,
952                         &stats[2]);
953  if (has_alpha) {
954    VP8SSIMAccumulatePlane(pic1->a, pic1->a_stride,
955                           pic2->a, pic2->a_stride,
956                           pic1->width, pic1->height, &stats[3]);
957  }
958  for (c = 0; c <= 4; ++c) {
959    if (type == 1) {
960      const double v = VP8SSIMGet(&stats[c]);
961      result[c] = (float)((v < 1.) ? -10.0 * log10(1. - v)
962                                   : kMinDistortion_dB);
963    } else {
964      const double v = VP8SSIMGetSquaredError(&stats[c]);
965      result[c] = (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
966                                   : kMinDistortion_dB);
967    }
968    // Accumulate forward
969    if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
970  }
971  return 1;
972}
973
974//------------------------------------------------------------------------------
975// Simplest high-level calls:
976
977typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int);
978
979static size_t Encode(const uint8_t* rgba, int width, int height, int stride,
980                     Importer import, float quality_factor, int lossless,
981                     uint8_t** output) {
982  WebPPicture pic;
983  WebPConfig config;
984  WebPMemoryWriter wrt;
985  int ok;
986
987  if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality_factor) ||
988      !WebPPictureInit(&pic)) {
989    return 0;  // shouldn't happen, except if system installation is broken
990  }
991
992  config.lossless = !!lossless;
993  pic.use_argb = !!lossless;
994  pic.width = width;
995  pic.height = height;
996  pic.writer = WebPMemoryWrite;
997  pic.custom_ptr = &wrt;
998  WebPMemoryWriterInit(&wrt);
999
1000  ok = import(&pic, rgba, stride) && WebPEncode(&config, &pic);
1001  WebPPictureFree(&pic);
1002  if (!ok) {
1003    free(wrt.mem);
1004    *output = NULL;
1005    return 0;
1006  }
1007  *output = wrt.mem;
1008  return wrt.size;
1009}
1010
1011#define ENCODE_FUNC(NAME, IMPORTER)                                     \
1012size_t NAME(const uint8_t* in, int w, int h, int bps, float q,          \
1013            uint8_t** out) {                                            \
1014  return Encode(in, w, h, bps, IMPORTER, q, 0, out);                    \
1015}
1016
1017ENCODE_FUNC(WebPEncodeRGB, WebPPictureImportRGB);
1018ENCODE_FUNC(WebPEncodeBGR, WebPPictureImportBGR);
1019ENCODE_FUNC(WebPEncodeRGBA, WebPPictureImportRGBA);
1020ENCODE_FUNC(WebPEncodeBGRA, WebPPictureImportBGRA);
1021
1022#undef ENCODE_FUNC
1023
1024#define LOSSLESS_DEFAULT_QUALITY 70.
1025#define LOSSLESS_ENCODE_FUNC(NAME, IMPORTER)                                 \
1026size_t NAME(const uint8_t* in, int w, int h, int bps, uint8_t** out) {       \
1027  return Encode(in, w, h, bps, IMPORTER, LOSSLESS_DEFAULT_QUALITY, 1, out);  \
1028}
1029
1030LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGB, WebPPictureImportRGB);
1031LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGR, WebPPictureImportBGR);
1032LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGBA, WebPPictureImportRGBA);
1033LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGRA, WebPPictureImportBGRA);
1034
1035#undef LOSSLESS_ENCODE_FUNC
1036
1037//------------------------------------------------------------------------------
1038
1039#if defined(__cplusplus) || defined(c_plusplus)
1040}    // extern "C"
1041#endif
1042