picture.c revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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    dst->y_stride = src->y_stride;
294    dst->uv_stride = src->uv_stride;
295    if (src->a != NULL) {
296      dst->a = src->a + top * src->a_stride + left;
297      dst->a_stride = src->a_stride;
298    }
299#ifdef WEBP_EXPERIMENTAL_FEATURES
300    if (src->u0 != NULL) {
301      const int left_pos =
302          IS_YUV_CSP(dst->colorspace, WEBP_YUV422) ? (left >> 1) : left;
303      dst->u0 = src->u0 + top * src->uv0_stride + left_pos;
304      dst->v0 = src->v0 + top * src->uv0_stride + left_pos;
305      dst->uv0_stride = src->uv0_stride;
306    }
307#endif
308  } else {
309    dst->argb = src->argb + top * src->argb_stride + left;
310    dst->argb_stride = src->argb_stride;
311  }
312  return 1;
313}
314
315//------------------------------------------------------------------------------
316// Picture cropping
317
318int WebPPictureCrop(WebPPicture* pic,
319                    int left, int top, int width, int height) {
320  WebPPicture tmp;
321
322  if (pic == NULL) return 0;
323  if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0;
324
325  WebPPictureGrabSpecs(pic, &tmp);
326  tmp.width = width;
327  tmp.height = height;
328  if (!WebPPictureAlloc(&tmp)) return 0;
329
330  if (!pic->use_argb) {
331    const int y_offset = top * pic->y_stride + left;
332    const int uv_offset = (top / 2) * pic->uv_stride + left / 2;
333    CopyPlane(pic->y + y_offset, pic->y_stride,
334              tmp.y, tmp.y_stride, width, height);
335    CopyPlane(pic->u + uv_offset, pic->uv_stride,
336              tmp.u, tmp.uv_stride, HALVE(width), HALVE(height));
337    CopyPlane(pic->v + uv_offset, pic->uv_stride,
338              tmp.v, tmp.uv_stride, HALVE(width), HALVE(height));
339
340    if (tmp.a != NULL) {
341      const int a_offset = top * pic->a_stride + left;
342      CopyPlane(pic->a + a_offset, pic->a_stride,
343                tmp.a, tmp.a_stride, width, height);
344    }
345#ifdef WEBP_EXPERIMENTAL_FEATURES
346    if (tmp.u0 != NULL) {
347      int w = width;
348      int left_pos = left;
349      if (IS_YUV_CSP(tmp.colorspace, WEBP_YUV422)) {
350        w = HALVE(w);
351        left_pos = HALVE(left_pos);
352      }
353      CopyPlane(pic->u0 + top * pic->uv0_stride + left_pos, pic->uv0_stride,
354                tmp.u0, tmp.uv0_stride, w, height);
355      CopyPlane(pic->v0 + top * pic->uv0_stride + left_pos, pic->uv0_stride,
356                tmp.v0, tmp.uv0_stride, w, height);
357    }
358#endif
359  } else {
360    const uint8_t* const src =
361        (const uint8_t*)(pic->argb + top * pic->argb_stride + left);
362    CopyPlane(src, pic->argb_stride * 4,
363              (uint8_t*)tmp.argb, tmp.argb_stride * 4,
364              width * 4, height);
365  }
366  WebPPictureFree(pic);
367  *pic = tmp;
368  return 1;
369}
370
371//------------------------------------------------------------------------------
372// Simple picture rescaler
373
374static void RescalePlane(const uint8_t* src,
375                         int src_width, int src_height, int src_stride,
376                         uint8_t* dst,
377                         int dst_width, int dst_height, int dst_stride,
378                         int32_t* const work,
379                         int num_channels) {
380  WebPRescaler rescaler;
381  int y = 0;
382  WebPRescalerInit(&rescaler, src_width, src_height,
383                   dst, dst_width, dst_height, dst_stride,
384                   num_channels,
385                   src_width, dst_width,
386                   src_height, dst_height,
387                   work);
388  memset(work, 0, 2 * dst_width * num_channels * sizeof(*work));
389  while (y < src_height) {
390    y += WebPRescalerImport(&rescaler, src_height - y,
391                            src + y * src_stride, src_stride);
392    WebPRescalerExport(&rescaler);
393  }
394}
395
396int WebPPictureRescale(WebPPicture* pic, int width, int height) {
397  WebPPicture tmp;
398  int prev_width, prev_height;
399  int32_t* work;
400
401  if (pic == NULL) return 0;
402  prev_width = pic->width;
403  prev_height = pic->height;
404  // if width is unspecified, scale original proportionally to height ratio.
405  if (width == 0) {
406    width = (prev_width * height + prev_height / 2) / prev_height;
407  }
408  // if height is unspecified, scale original proportionally to width ratio.
409  if (height == 0) {
410    height = (prev_height * width + prev_width / 2) / prev_width;
411  }
412  // Check if the overall dimensions still make sense.
413  if (width <= 0 || height <= 0) return 0;
414
415  WebPPictureGrabSpecs(pic, &tmp);
416  tmp.width = width;
417  tmp.height = height;
418  if (!WebPPictureAlloc(&tmp)) return 0;
419
420  if (!pic->use_argb) {
421    work = (int32_t*)WebPSafeMalloc(2ULL * width, sizeof(*work));
422    if (work == NULL) {
423      WebPPictureFree(&tmp);
424      return 0;
425    }
426
427    RescalePlane(pic->y, prev_width, prev_height, pic->y_stride,
428                 tmp.y, width, height, tmp.y_stride, work, 1);
429    RescalePlane(pic->u,
430                 HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
431                 tmp.u,
432                 HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
433    RescalePlane(pic->v,
434                 HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
435                 tmp.v,
436                 HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
437
438    if (tmp.a != NULL) {
439      RescalePlane(pic->a, prev_width, prev_height, pic->a_stride,
440                   tmp.a, width, height, tmp.a_stride, work, 1);
441    }
442#ifdef WEBP_EXPERIMENTAL_FEATURES
443    if (tmp.u0 != NULL) {
444      const int s = IS_YUV_CSP(tmp.colorspace, WEBP_YUV422) ? 2 : 1;
445      RescalePlane(
446          pic->u0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride,
447          tmp.u0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1);
448      RescalePlane(
449          pic->v0, (prev_width + s / 2) / s, prev_height, pic->uv0_stride,
450          tmp.v0, (width + s / 2) / s, height, tmp.uv0_stride, work, 1);
451    }
452#endif
453  } else {
454    work = (int32_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work));
455    if (work == NULL) {
456      WebPPictureFree(&tmp);
457      return 0;
458    }
459
460    RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height,
461                 pic->argb_stride * 4,
462                 (uint8_t*)tmp.argb, width, height,
463                 tmp.argb_stride * 4,
464                 work, 4);
465
466  }
467  WebPPictureFree(pic);
468  free(work);
469  *pic = tmp;
470  return 1;
471}
472
473//------------------------------------------------------------------------------
474// WebPMemoryWriter: Write-to-memory
475
476void WebPMemoryWriterInit(WebPMemoryWriter* writer) {
477  writer->mem = NULL;
478  writer->size = 0;
479  writer->max_size = 0;
480}
481
482int WebPMemoryWrite(const uint8_t* data, size_t data_size,
483                    const WebPPicture* picture) {
484  WebPMemoryWriter* const w = (WebPMemoryWriter*)picture->custom_ptr;
485  uint64_t next_size;
486  if (w == NULL) {
487    return 1;
488  }
489  next_size = (uint64_t)w->size + data_size;
490  if (next_size > w->max_size) {
491    uint8_t* new_mem;
492    uint64_t next_max_size = 2ULL * w->max_size;
493    if (next_max_size < next_size) next_max_size = next_size;
494    if (next_max_size < 8192ULL) next_max_size = 8192ULL;
495    new_mem = (uint8_t*)WebPSafeMalloc(next_max_size, 1);
496    if (new_mem == NULL) {
497      return 0;
498    }
499    if (w->size > 0) {
500      memcpy(new_mem, w->mem, w->size);
501    }
502    free(w->mem);
503    w->mem = new_mem;
504    // down-cast is ok, thanks to WebPSafeMalloc
505    w->max_size = (size_t)next_max_size;
506  }
507  if (data_size > 0) {
508    memcpy(w->mem + w->size, data, data_size);
509    w->size += data_size;
510  }
511  return 1;
512}
513
514//------------------------------------------------------------------------------
515// Detection of non-trivial transparency
516
517// Returns true if alpha[] has non-0xff values.
518static int CheckNonOpaque(const uint8_t* alpha, int width, int height,
519                          int x_step, int y_step) {
520  if (alpha == NULL) return 0;
521  while (height-- > 0) {
522    int x;
523    for (x = 0; x < width * x_step; x += x_step) {
524      if (alpha[x] != 0xff) return 1;  // TODO(skal): check 4/8 bytes at a time.
525    }
526    alpha += y_step;
527  }
528  return 0;
529}
530
531// Checking for the presence of non-opaque alpha.
532int WebPPictureHasTransparency(const WebPPicture* picture) {
533  if (picture == NULL) return 0;
534  if (!picture->use_argb) {
535    return CheckNonOpaque(picture->a, picture->width, picture->height,
536                          1, picture->a_stride);
537  } else {
538    int x, y;
539    const uint32_t* argb = picture->argb;
540    if (argb == NULL) return 0;
541    for (y = 0; y < picture->height; ++y) {
542      for (x = 0; x < picture->width; ++x) {
543        if (argb[x] < 0xff000000u) return 1;   // test any alpha values != 0xff
544      }
545      argb += picture->argb_stride;
546    }
547  }
548  return 0;
549}
550
551//------------------------------------------------------------------------------
552// RGB -> YUV conversion
553
554// TODO: we can do better than simply 2x2 averaging on U/V samples.
555#define SUM4(ptr) ((ptr)[0] + (ptr)[step] + \
556                   (ptr)[rgb_stride] + (ptr)[rgb_stride + step])
557#define SUM2H(ptr) (2 * (ptr)[0] + 2 * (ptr)[step])
558#define SUM2V(ptr) (2 * (ptr)[0] + 2 * (ptr)[rgb_stride])
559#define SUM1(ptr)  (4 * (ptr)[0])
560#define RGB_TO_UV(x, y, SUM) {                           \
561  const int src = (2 * (step * (x) + (y) * rgb_stride)); \
562  const int dst = (x) + (y) * picture->uv_stride;        \
563  const int r = SUM(r_ptr + src);                        \
564  const int g = SUM(g_ptr + src);                        \
565  const int b = SUM(b_ptr + src);                        \
566  picture->u[dst] = VP8RGBToU(r, g, b);                  \
567  picture->v[dst] = VP8RGBToV(r, g, b);                  \
568}
569
570#define RGB_TO_UV0(x_in, x_out, y, SUM) {                \
571  const int src = (step * (x_in) + (y) * rgb_stride);    \
572  const int dst = (x_out) + (y) * picture->uv0_stride;   \
573  const int r = SUM(r_ptr + src);                        \
574  const int g = SUM(g_ptr + src);                        \
575  const int b = SUM(b_ptr + src);                        \
576  picture->u0[dst] = VP8RGBToU(r, g, b);                 \
577  picture->v0[dst] = VP8RGBToV(r, g, b);                 \
578}
579
580static void MakeGray(WebPPicture* const picture) {
581  int y;
582  const int uv_width = HALVE(picture->width);
583  const int uv_height = HALVE(picture->height);
584  for (y = 0; y < uv_height; ++y) {
585    memset(picture->u + y * picture->uv_stride, 128, uv_width);
586    memset(picture->v + y * picture->uv_stride, 128, uv_width);
587  }
588}
589
590static int ImportYUVAFromRGBA(const uint8_t* const r_ptr,
591                              const uint8_t* const g_ptr,
592                              const uint8_t* const b_ptr,
593                              const uint8_t* const a_ptr,
594                              int step,         // bytes per pixel
595                              int rgb_stride,   // bytes per scanline
596                              WebPPicture* const picture) {
597  const WebPEncCSP uv_csp = picture->colorspace & WEBP_CSP_UV_MASK;
598  int x, y;
599  const int width = picture->width;
600  const int height = picture->height;
601  const int has_alpha = CheckNonOpaque(a_ptr, width, height, step, rgb_stride);
602
603  picture->colorspace = uv_csp;
604  picture->use_argb = 0;
605  if (has_alpha) {
606    picture->colorspace |= WEBP_CSP_ALPHA_BIT;
607  }
608  if (!WebPPictureAlloc(picture)) return 0;
609
610  // Import luma plane
611  for (y = 0; y < height; ++y) {
612    for (x = 0; x < width; ++x) {
613      const int offset = step * x + y * rgb_stride;
614      picture->y[x + y * picture->y_stride] =
615          VP8RGBToY(r_ptr[offset], g_ptr[offset], b_ptr[offset]);
616    }
617  }
618
619  // Downsample U/V plane
620  if (uv_csp != WEBP_YUV400) {
621    for (y = 0; y < (height >> 1); ++y) {
622      for (x = 0; x < (width >> 1); ++x) {
623        RGB_TO_UV(x, y, SUM4);
624      }
625      if (width & 1) {
626        RGB_TO_UV(x, y, SUM2V);
627      }
628    }
629    if (height & 1) {
630      for (x = 0; x < (width >> 1); ++x) {
631        RGB_TO_UV(x, y, SUM2H);
632      }
633      if (width & 1) {
634        RGB_TO_UV(x, y, SUM1);
635      }
636    }
637
638#ifdef WEBP_EXPERIMENTAL_FEATURES
639    // Store original U/V samples too
640    if (uv_csp == WEBP_YUV422) {
641      for (y = 0; y < height; ++y) {
642        for (x = 0; x < (width >> 1); ++x) {
643          RGB_TO_UV0(2 * x, x, y, SUM2H);
644        }
645        if (width & 1) {
646          RGB_TO_UV0(2 * x, x, y, SUM1);
647        }
648      }
649    } else if (uv_csp == WEBP_YUV444) {
650      for (y = 0; y < height; ++y) {
651        for (x = 0; x < width; ++x) {
652          RGB_TO_UV0(x, x, y, SUM1);
653        }
654      }
655    }
656#endif
657  } else {
658    MakeGray(picture);
659  }
660
661  if (has_alpha) {
662    assert(step >= 4);
663    for (y = 0; y < height; ++y) {
664      for (x = 0; x < width; ++x) {
665        picture->a[x + y * picture->a_stride] =
666            a_ptr[step * x + y * rgb_stride];
667      }
668    }
669  }
670  return 1;
671}
672
673static int Import(WebPPicture* const picture,
674                  const uint8_t* const rgb, int rgb_stride,
675                  int step, int swap_rb, int import_alpha) {
676  const uint8_t* const r_ptr = rgb + (swap_rb ? 2 : 0);
677  const uint8_t* const g_ptr = rgb + 1;
678  const uint8_t* const b_ptr = rgb + (swap_rb ? 0 : 2);
679  const uint8_t* const a_ptr = import_alpha ? rgb + 3 : NULL;
680  const int width = picture->width;
681  const int height = picture->height;
682
683  if (!picture->use_argb) {
684    return ImportYUVAFromRGBA(r_ptr, g_ptr, b_ptr, a_ptr, step, rgb_stride,
685                              picture);
686  }
687  if (import_alpha) {
688    picture->colorspace |= WEBP_CSP_ALPHA_BIT;
689  } else {
690    picture->colorspace &= ~WEBP_CSP_ALPHA_BIT;
691  }
692  if (!WebPPictureAlloc(picture)) return 0;
693
694  if (!import_alpha) {
695    int x, y;
696    for (y = 0; y < height; ++y) {
697      for (x = 0; x < width; ++x) {
698        const int offset = step * x + y * rgb_stride;
699        const uint32_t argb =
700            0xff000000u |
701            (r_ptr[offset] << 16) |
702            (g_ptr[offset] <<  8) |
703            (b_ptr[offset]);
704        picture->argb[x + y * picture->argb_stride] = argb;
705      }
706    }
707  } else {
708    int x, y;
709    assert(step >= 4);
710    for (y = 0; y < height; ++y) {
711      for (x = 0; x < width; ++x) {
712        const int offset = step * x + y * rgb_stride;
713        const uint32_t argb = (a_ptr[offset] << 24) |
714                              (r_ptr[offset] << 16) |
715                              (g_ptr[offset] <<  8) |
716                              (b_ptr[offset]);
717        picture->argb[x + y * picture->argb_stride] = argb;
718      }
719    }
720  }
721  return 1;
722}
723#undef SUM4
724#undef SUM2V
725#undef SUM2H
726#undef SUM1
727#undef RGB_TO_UV
728
729int WebPPictureImportRGB(WebPPicture* picture,
730                         const uint8_t* rgb, int rgb_stride) {
731  return Import(picture, rgb, rgb_stride, 3, 0, 0);
732}
733
734int WebPPictureImportBGR(WebPPicture* picture,
735                         const uint8_t* rgb, int rgb_stride) {
736  return Import(picture, rgb, rgb_stride, 3, 1, 0);
737}
738
739int WebPPictureImportRGBA(WebPPicture* picture,
740                          const uint8_t* rgba, int rgba_stride) {
741  return Import(picture, rgba, rgba_stride, 4, 0, 1);
742}
743
744int WebPPictureImportBGRA(WebPPicture* picture,
745                          const uint8_t* rgba, int rgba_stride) {
746  return Import(picture, rgba, rgba_stride, 4, 1, 1);
747}
748
749int WebPPictureImportRGBX(WebPPicture* picture,
750                          const uint8_t* rgba, int rgba_stride) {
751  return Import(picture, rgba, rgba_stride, 4, 0, 0);
752}
753
754int WebPPictureImportBGRX(WebPPicture* picture,
755                          const uint8_t* rgba, int rgba_stride) {
756  return Import(picture, rgba, rgba_stride, 4, 1, 0);
757}
758
759//------------------------------------------------------------------------------
760// Automatic YUV <-> ARGB conversions.
761
762int WebPPictureYUVAToARGB(WebPPicture* picture) {
763  if (picture == NULL) return 0;
764  if (picture->memory_ == NULL || picture->y == NULL ||
765      picture->u == NULL || picture->v == NULL) {
766    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
767  }
768  if ((picture->colorspace & WEBP_CSP_ALPHA_BIT) && picture->a == NULL) {
769    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
770  }
771  if ((picture->colorspace & WEBP_CSP_UV_MASK) != WEBP_YUV420) {
772    return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION);
773  }
774  // Allocate a new argb buffer (discarding the previous one).
775  if (!PictureAllocARGB(picture)) return 0;
776
777  // Convert
778  {
779    int y;
780    const int width = picture->width;
781    const int height = picture->height;
782    const int argb_stride = 4 * picture->argb_stride;
783    uint8_t* dst = (uint8_t*)picture->argb;
784    const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y;
785    WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST);
786
787    // First row, with replicated top samples.
788    upsample(NULL, cur_y, cur_u, cur_v, cur_u, cur_v, NULL, dst, width);
789    cur_y += picture->y_stride;
790    dst += argb_stride;
791    // Center rows.
792    for (y = 1; y + 1 < height; y += 2) {
793      const uint8_t* const top_u = cur_u;
794      const uint8_t* const top_v = cur_v;
795      cur_u += picture->uv_stride;
796      cur_v += picture->uv_stride;
797      upsample(cur_y, cur_y + picture->y_stride, top_u, top_v, cur_u, cur_v,
798               dst, dst + argb_stride, width);
799      cur_y += 2 * picture->y_stride;
800      dst += 2 * argb_stride;
801    }
802    // Last row (if needed), with replicated bottom samples.
803    if (height > 1 && !(height & 1)) {
804      upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width);
805    }
806    // Insert alpha values if needed, in replacement for the default 0xff ones.
807    if (picture->colorspace & WEBP_CSP_ALPHA_BIT) {
808      for (y = 0; y < height; ++y) {
809        uint32_t* const argb_dst = picture->argb + y * picture->argb_stride;
810        const uint8_t* const src = picture->a + y * picture->a_stride;
811        int x;
812        for (x = 0; x < width; ++x) {
813          argb_dst[x] = (argb_dst[x] & 0x00ffffffu) | (src[x] << 24);
814        }
815      }
816    }
817  }
818  return 1;
819}
820
821int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) {
822  if (picture == NULL) return 0;
823  if (picture->argb == NULL) {
824    return WebPEncodingSetError(picture, VP8_ENC_ERROR_NULL_PARAMETER);
825  } else {
826    const uint8_t* const argb = (const uint8_t*)picture->argb;
827    const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1;
828    const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2;
829    const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3;
830    const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0;
831    // We work on a tmp copy of 'picture', because ImportYUVAFromRGBA()
832    // would be calling WebPPictureFree(picture) otherwise.
833    WebPPicture tmp = *picture;
834    PictureResetARGB(&tmp);  // reset ARGB buffer so that it's not free()'d.
835    tmp.use_argb = 0;
836    tmp.colorspace = colorspace & WEBP_CSP_UV_MASK;
837    if (!ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, &tmp)) {
838      return WebPEncodingSetError(picture, VP8_ENC_ERROR_OUT_OF_MEMORY);
839    }
840    // Copy back the YUV specs into 'picture'.
841    tmp.argb = picture->argb;
842    tmp.argb_stride = picture->argb_stride;
843    tmp.memory_argb_ = picture->memory_argb_;
844    *picture = tmp;
845  }
846  return 1;
847}
848
849//------------------------------------------------------------------------------
850// Helper: clean up fully transparent area to help compressibility.
851
852#define SIZE 8
853#define SIZE2 (SIZE / 2)
854static int is_transparent_area(const uint8_t* ptr, int stride, int size) {
855  int y, x;
856  for (y = 0; y < size; ++y) {
857    for (x = 0; x < size; ++x) {
858      if (ptr[x]) {
859        return 0;
860      }
861    }
862    ptr += stride;
863  }
864  return 1;
865}
866
867static WEBP_INLINE void flatten(uint8_t* ptr, int v, int stride, int size) {
868  int y;
869  for (y = 0; y < size; ++y) {
870    memset(ptr, v, size);
871    ptr += stride;
872  }
873}
874
875void WebPCleanupTransparentArea(WebPPicture* pic) {
876  int x, y, w, h;
877  const uint8_t* a_ptr;
878  int values[3] = { 0 };
879
880  if (pic == NULL) return;
881
882  a_ptr = pic->a;
883  if (a_ptr == NULL) return;    // nothing to do
884
885  w = pic->width / SIZE;
886  h = pic->height / SIZE;
887  for (y = 0; y < h; ++y) {
888    int need_reset = 1;
889    for (x = 0; x < w; ++x) {
890      const int off_a = (y * pic->a_stride + x) * SIZE;
891      const int off_y = (y * pic->y_stride + x) * SIZE;
892      const int off_uv = (y * pic->uv_stride + x) * SIZE2;
893      if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) {
894        if (need_reset) {
895          values[0] = pic->y[off_y];
896          values[1] = pic->u[off_uv];
897          values[2] = pic->v[off_uv];
898          need_reset = 0;
899        }
900        flatten(pic->y + off_y, values[0], pic->y_stride, SIZE);
901        flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2);
902        flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2);
903      } else {
904        need_reset = 1;
905      }
906    }
907    // ignore the left-overs on right/bottom
908  }
909}
910
911#undef SIZE
912#undef SIZE2
913
914//------------------------------------------------------------------------------
915// local-min distortion
916//
917// For every pixel in the *reference* picture, we search for the local best
918// match in the compressed image. This is not a symmetrical measure.
919
920// search radius. Shouldn't be too large.
921#define RADIUS 2
922
923static float AccumulateLSIM(const uint8_t* src, int src_stride,
924                            const uint8_t* ref, int ref_stride,
925                            int w, int h) {
926  int x, y;
927  double total_sse = 0.;
928  for (y = 0; y < h; ++y) {
929    const int y_0 = (y - RADIUS < 0) ? 0 : y - RADIUS;
930    const int y_1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1;
931    for (x = 0; x < w; ++x) {
932      const int x_0 = (x - RADIUS < 0) ? 0 : x - RADIUS;
933      const int x_1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1;
934      double best_sse = 255. * 255.;
935      const double value = (double)ref[y * ref_stride + x];
936      int i, j;
937      for (j = y_0; j < y_1; ++j) {
938        const uint8_t* s = src + j * src_stride;
939        for (i = x_0; i < x_1; ++i) {
940          const double sse = (double)(s[i] - value) * (s[i] - value);
941          if (sse < best_sse) best_sse = sse;
942        }
943      }
944      total_sse += best_sse;
945    }
946  }
947  return (float)total_sse;
948}
949#undef RADIUS
950
951//------------------------------------------------------------------------------
952// Distortion
953
954// Max value returned in case of exact similarity.
955static const double kMinDistortion_dB = 99.;
956static float GetPSNR(const double v) {
957  return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
958                          : kMinDistortion_dB);
959}
960
961int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref,
962                          int type, float result[5]) {
963  DistoStats stats[5];
964  int has_alpha;
965  int uv_w, uv_h;
966
967  if (src == NULL || ref == NULL ||
968      src->width != ref->width || src->height != ref->height ||
969      src->y == NULL || ref->y == NULL ||
970      src->u == NULL || ref->u == NULL ||
971      src->v == NULL || ref->v == NULL ||
972      result == NULL) {
973    return 0;
974  }
975  // TODO(skal): provide distortion for ARGB too.
976  if (src->use_argb == 1 || src->use_argb != ref->use_argb) {
977    return 0;
978  }
979
980  has_alpha = !!(src->colorspace & WEBP_CSP_ALPHA_BIT);
981  if (has_alpha != !!(ref->colorspace & WEBP_CSP_ALPHA_BIT) ||
982      (has_alpha && (src->a == NULL || ref->a == NULL))) {
983    return 0;
984  }
985
986  memset(stats, 0, sizeof(stats));
987
988  uv_w = HALVE(src->width);
989  uv_h = HALVE(src->height);
990  if (type >= 2) {
991    float sse[4];
992    sse[0] = AccumulateLSIM(src->y, src->y_stride,
993                            ref->y, ref->y_stride, src->width, src->height);
994    sse[1] = AccumulateLSIM(src->u, src->uv_stride,
995                            ref->u, ref->uv_stride, uv_w, uv_h);
996    sse[2] = AccumulateLSIM(src->v, src->uv_stride,
997                            ref->v, ref->uv_stride, uv_w, uv_h);
998    sse[3] = has_alpha ? AccumulateLSIM(src->a, src->a_stride,
999                                        ref->a, ref->a_stride,
1000                                        src->width, src->height)
1001                       : 0.f;
1002    result[0] = GetPSNR(sse[0] / (src->width * src->height));
1003    result[1] = GetPSNR(sse[1] / (uv_w * uv_h));
1004    result[2] = GetPSNR(sse[2] / (uv_w * uv_h));
1005    result[3] = GetPSNR(sse[3] / (src->width * src->height));
1006    {
1007      double total_sse = sse[0] + sse[1] + sse[2];
1008      int total_pixels = src->width * src->height + 2 * uv_w * uv_h;
1009      if (has_alpha) {
1010        total_pixels += src->width * src->height;
1011        total_sse += sse[3];
1012      }
1013      result[4] = GetPSNR(total_sse / total_pixels);
1014    }
1015  } else {
1016    int c;
1017    VP8SSIMAccumulatePlane(src->y, src->y_stride,
1018                           ref->y, ref->y_stride,
1019                           src->width, src->height, &stats[0]);
1020    VP8SSIMAccumulatePlane(src->u, src->uv_stride,
1021                           ref->u, ref->uv_stride,
1022                           uv_w, uv_h, &stats[1]);
1023    VP8SSIMAccumulatePlane(src->v, src->uv_stride,
1024                           ref->v, ref->uv_stride,
1025                           uv_w, uv_h, &stats[2]);
1026    if (has_alpha) {
1027      VP8SSIMAccumulatePlane(src->a, src->a_stride,
1028                             ref->a, ref->a_stride,
1029                             src->width, src->height, &stats[3]);
1030    }
1031    for (c = 0; c <= 4; ++c) {
1032      if (type == 1) {
1033        const double v = VP8SSIMGet(&stats[c]);
1034        result[c] = (float)((v < 1.) ? -10.0 * log10(1. - v)
1035                                     : kMinDistortion_dB);
1036      } else {
1037        const double v = VP8SSIMGetSquaredError(&stats[c]);
1038        result[c] = GetPSNR(v);
1039      }
1040      // Accumulate forward
1041      if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
1042    }
1043  }
1044  return 1;
1045}
1046
1047//------------------------------------------------------------------------------
1048// Simplest high-level calls:
1049
1050typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int);
1051
1052static size_t Encode(const uint8_t* rgba, int width, int height, int stride,
1053                     Importer import, float quality_factor, int lossless,
1054                     uint8_t** output) {
1055  WebPPicture pic;
1056  WebPConfig config;
1057  WebPMemoryWriter wrt;
1058  int ok;
1059
1060  if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality_factor) ||
1061      !WebPPictureInit(&pic)) {
1062    return 0;  // shouldn't happen, except if system installation is broken
1063  }
1064
1065  config.lossless = !!lossless;
1066  pic.use_argb = !!lossless;
1067  pic.width = width;
1068  pic.height = height;
1069  pic.writer = WebPMemoryWrite;
1070  pic.custom_ptr = &wrt;
1071  WebPMemoryWriterInit(&wrt);
1072
1073  ok = import(&pic, rgba, stride) && WebPEncode(&config, &pic);
1074  WebPPictureFree(&pic);
1075  if (!ok) {
1076    free(wrt.mem);
1077    *output = NULL;
1078    return 0;
1079  }
1080  *output = wrt.mem;
1081  return wrt.size;
1082}
1083
1084#define ENCODE_FUNC(NAME, IMPORTER)                                     \
1085size_t NAME(const uint8_t* in, int w, int h, int bps, float q,          \
1086            uint8_t** out) {                                            \
1087  return Encode(in, w, h, bps, IMPORTER, q, 0, out);                    \
1088}
1089
1090ENCODE_FUNC(WebPEncodeRGB, WebPPictureImportRGB);
1091ENCODE_FUNC(WebPEncodeBGR, WebPPictureImportBGR);
1092ENCODE_FUNC(WebPEncodeRGBA, WebPPictureImportRGBA);
1093ENCODE_FUNC(WebPEncodeBGRA, WebPPictureImportBGRA);
1094
1095#undef ENCODE_FUNC
1096
1097#define LOSSLESS_DEFAULT_QUALITY 70.
1098#define LOSSLESS_ENCODE_FUNC(NAME, IMPORTER)                                 \
1099size_t NAME(const uint8_t* in, int w, int h, int bps, uint8_t** out) {       \
1100  return Encode(in, w, h, bps, IMPORTER, LOSSLESS_DEFAULT_QUALITY, 1, out);  \
1101}
1102
1103LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGB, WebPPictureImportRGB);
1104LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGR, WebPPictureImportBGR);
1105LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGBA, WebPPictureImportRGBA);
1106LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGRA, WebPPictureImportBGRA);
1107
1108#undef LOSSLESS_ENCODE_FUNC
1109
1110//------------------------------------------------------------------------------
1111
1112#if defined(__cplusplus) || defined(c_plusplus)
1113}    // extern "C"
1114#endif
1115