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)
23 extern "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
29 static 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
WebPPictureAlloc(WebPPicture * picture)39 int 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).
PictureResetARGB(WebPPicture * const picture)140 static 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).
PictureResetYUVA(WebPPicture * const picture)147 static 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.
WebPPictureGrabSpecs(const WebPPicture * const src,WebPPicture * const dst)158 static 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.
PictureAllocARGB(WebPPicture * const picture)168 static 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).
WebPPictureFree(WebPPicture * picture)184 void 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).
CopyPlane(const uint8_t * src,int src_stride,uint8_t * dst,int dst_stride,int width,int height)197 static 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.
SnapTopLeftPosition(const WebPPicture * const pic,int * const left,int * const top)207 static 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.
AdjustAndCheckRectangle(const WebPPicture * const pic,int * const left,int * const top,int width,int height)219 static 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
WebPPictureCopy(const WebPPicture * src,WebPPicture * dst)230 int 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
WebPPictureIsView(const WebPPicture * picture)268 int 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
WebPPictureView(const WebPPicture * src,int left,int top,int width,int height,WebPPicture * dst)276 int 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
WebPPictureCrop(WebPPicture * pic,int left,int top,int width,int height)318 int 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
RescalePlane(const uint8_t * src,int src_width,int src_height,int src_stride,uint8_t * dst,int dst_width,int dst_height,int dst_stride,int32_t * const work,int num_channels)374 static 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
WebPPictureRescale(WebPPicture * pic,int width,int height)396 int 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
WebPMemoryWriterInit(WebPMemoryWriter * writer)476 void WebPMemoryWriterInit(WebPMemoryWriter* writer) {
477 writer->mem = NULL;
478 writer->size = 0;
479 writer->max_size = 0;
480 }
481
WebPMemoryWrite(const uint8_t * data,size_t data_size,const WebPPicture * picture)482 int 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.
CheckNonOpaque(const uint8_t * alpha,int width,int height,int x_step,int y_step)518 static 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.
WebPPictureHasTransparency(const WebPPicture * picture)532 int 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
MakeGray(WebPPicture * const picture)580 static 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
ImportYUVAFromRGBA(const uint8_t * const r_ptr,const uint8_t * const g_ptr,const uint8_t * const b_ptr,const uint8_t * const a_ptr,int step,int rgb_stride,WebPPicture * const picture)590 static 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
Import(WebPPicture * const picture,const uint8_t * const rgb,int rgb_stride,int step,int swap_rb,int import_alpha)673 static 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
WebPPictureImportRGB(WebPPicture * picture,const uint8_t * rgb,int rgb_stride)729 int WebPPictureImportRGB(WebPPicture* picture,
730 const uint8_t* rgb, int rgb_stride) {
731 return Import(picture, rgb, rgb_stride, 3, 0, 0);
732 }
733
WebPPictureImportBGR(WebPPicture * picture,const uint8_t * rgb,int rgb_stride)734 int WebPPictureImportBGR(WebPPicture* picture,
735 const uint8_t* rgb, int rgb_stride) {
736 return Import(picture, rgb, rgb_stride, 3, 1, 0);
737 }
738
WebPPictureImportRGBA(WebPPicture * picture,const uint8_t * rgba,int rgba_stride)739 int WebPPictureImportRGBA(WebPPicture* picture,
740 const uint8_t* rgba, int rgba_stride) {
741 return Import(picture, rgba, rgba_stride, 4, 0, 1);
742 }
743
WebPPictureImportBGRA(WebPPicture * picture,const uint8_t * rgba,int rgba_stride)744 int WebPPictureImportBGRA(WebPPicture* picture,
745 const uint8_t* rgba, int rgba_stride) {
746 return Import(picture, rgba, rgba_stride, 4, 1, 1);
747 }
748
WebPPictureImportRGBX(WebPPicture * picture,const uint8_t * rgba,int rgba_stride)749 int WebPPictureImportRGBX(WebPPicture* picture,
750 const uint8_t* rgba, int rgba_stride) {
751 return Import(picture, rgba, rgba_stride, 4, 0, 0);
752 }
753
WebPPictureImportBGRX(WebPPicture * picture,const uint8_t * rgba,int rgba_stride)754 int 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
WebPPictureYUVAToARGB(WebPPicture * picture)762 int 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
WebPPictureARGBToYUVA(WebPPicture * picture,WebPEncCSP colorspace)821 int 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)
is_transparent_area(const uint8_t * ptr,int stride,int size)854 static 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
flatten(uint8_t * ptr,int v,int stride,int size)867 static 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
WebPCleanupTransparentArea(WebPPicture * pic)875 void 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
AccumulateLSIM(const uint8_t * src,int src_stride,const uint8_t * ref,int ref_stride,int w,int h)923 static 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.
955 static const double kMinDistortion_dB = 99.;
GetPSNR(const double v)956 static float GetPSNR(const double v) {
957 return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
958 : kMinDistortion_dB);
959 }
960
WebPPictureDistortion(const WebPPicture * src,const WebPPicture * ref,int type,float result[5])961 int 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
1050 typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int);
1051
Encode(const uint8_t * rgba,int width,int height,int stride,Importer import,float quality_factor,int lossless,uint8_t ** output)1052 static 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) \
1085 size_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
1090 ENCODE_FUNC(WebPEncodeRGB, WebPPictureImportRGB);
1091 ENCODE_FUNC(WebPEncodeBGR, WebPPictureImportBGR);
1092 ENCODE_FUNC(WebPEncodeRGBA, WebPPictureImportRGBA);
1093 ENCODE_FUNC(WebPEncodeBGRA, WebPPictureImportBGRA);
1094
1095 #undef ENCODE_FUNC
1096
1097 #define LOSSLESS_DEFAULT_QUALITY 70.
1098 #define LOSSLESS_ENCODE_FUNC(NAME, IMPORTER) \
1099 size_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
1103 LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGB, WebPPictureImportRGB);
1104 LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessBGR, WebPPictureImportBGR);
1105 LOSSLESS_ENCODE_FUNC(WebPEncodeLosslessRGBA, WebPPictureImportRGBA);
1106 LOSSLESS_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