1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "ycbcr_to_rgb565.h"
10 #include "nsAlgorithm.h"
14 #ifdef HAVE_YCBCR_TO_RGB565
20 /*This contains all of the parameters that are needed to convert a row.
21 Passing them in a struct instead of as individual parameters saves the need
22 to continually push onto the stack the ones that are fixed for every row.*/
23 struct yuv2rgb565_row_scale_bilinear_ctx
{
33 /*Not used for 4:4:4, except with chroma-nearest.*/
34 int source_uv_xoffs_q16
;
35 /*Not used for 4:4:4 or chroma-nearest.*/
37 /*Not used for 4:2:2, 4:4:4, or chroma-nearest.*/
43 /*This contains all of the parameters that are needed to convert a row.
44 Passing them in a struct instead of as individual parameters saves the need
45 to continually push onto the stack the ones that are fixed for every row.*/
46 struct yuv2rgb565_row_scale_nearest_ctx
{
54 /*Not used for 4:4:4.*/
55 int source_uv_xoffs_q16
;
60 typedef void (*yuv2rgb565_row_scale_bilinear_func
)(
61 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
);
63 typedef void (*yuv2rgb565_row_scale_nearest_func
)(
64 const yuv2rgb565_row_scale_nearest_ctx
*ctx
, int dither
);
68 //TODO: fix NEON asm for iOS
69 # if defined(MOZILLA_MAY_SUPPORT_NEON) && !defined(__APPLE__)
71 extern "C" void ScaleYCbCr42xToRGB565_BilinearY_Row_NEON(
72 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
);
74 void __attribute((noinline
)) yuv42x_to_rgb565_row_neon(uint16
*dst
,
85 /*Bilinear interpolation of a single value.
86 This uses the exact same formulas as the asm, even though it adds some extra
87 shifts that do nothing but reduce accuracy.*/
88 static int bislerp(const uint8_t *row
,
99 c
= row
[source_x
+pitch
];
100 d
= row
[source_x
+pitch
+1];
101 a
= ((a
<<8)+(c
-a
)*yweight
+128)>>8;
102 b
= ((b
<<8)+(d
-b
)*yweight
+128)>>8;
103 return ((a
<<8)+(b
-a
)*xweight
+128)>>8;
106 /*Convert a single pixel from Y'CbCr to RGB565.
107 This uses the exact same formulas as the asm, even though we could make the
108 constants a lot more accurate with 32-bit wide registers.*/
109 static uint16_t yu2rgb565(int y
, int u
, int v
, int dither
) {
110 /*This combines the constant offset that needs to be added during the Y'CbCr
111 conversion with a rounding offset that depends on the dither parameter.*/
112 static const int DITHER_BIAS
[4][3]={
113 {-14240, 8704, -17696},
114 {-14240+128,8704+64, -17696+128},
115 {-14240+256,8704+128,-17696+256},
116 {-14240+384,8704+192,-17696+384}
121 r
= clamped((74*y
+102*v
+DITHER_BIAS
[dither
][0])>>9, 0, 31);
122 g
= clamped((74*y
-25*u
-52*v
+DITHER_BIAS
[dither
][1])>>8, 0, 63);
123 b
= clamped((74*y
+129*u
+DITHER_BIAS
[dither
][2])>>9, 0, 31);
124 return (uint16_t)(r
<<11 | g
<<5 | b
);
127 static void ScaleYCbCr420ToRGB565_Bilinear_Row_C(
128 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
131 source_x_q16
= ctx
->source_x0_q16
;
132 for (x
= 0; x
< ctx
->width
; x
++) {
138 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
139 source_x
= source_x_q16
>>16;
140 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
141 xweight
= (((source_x_q16
+ctx
->source_uv_xoffs_q16
)&0x1FFFF)+256)>>9;
142 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
143 source_x_q16
+= ctx
->source_dx_q16
;
144 u
= bislerp(ctx
->u_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->uv_yweight
);
145 v
= bislerp(ctx
->v_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->uv_yweight
);
146 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
151 static void ScaleYCbCr422ToRGB565_Bilinear_Row_C(
152 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
155 source_x_q16
= ctx
->source_x0_q16
;
156 for (x
= 0; x
< ctx
->width
; x
++) {
162 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
163 source_x
= source_x_q16
>>16;
164 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
165 xweight
= (((source_x_q16
+ctx
->source_uv_xoffs_q16
)&0x1FFFF)+256)>>9;
166 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
167 source_x_q16
+= ctx
->source_dx_q16
;
168 u
= bislerp(ctx
->u_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->y_yweight
);
169 v
= bislerp(ctx
->v_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->y_yweight
);
170 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
175 static void ScaleYCbCr444ToRGB565_Bilinear_Row_C(
176 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
179 source_x_q16
= ctx
->source_x0_q16
;
180 for (x
= 0; x
< ctx
->width
; x
++) {
186 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
187 source_x
= source_x_q16
>>16;
188 source_x_q16
+= ctx
->source_dx_q16
;
189 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
190 u
= bislerp(ctx
->u_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
191 v
= bislerp(ctx
->v_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
192 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
197 static void ScaleYCbCr42xToRGB565_BilinearY_Row_C(
198 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
201 source_x_q16
= ctx
->source_x0_q16
;
202 for (x
= 0; x
< ctx
->width
; x
++) {
208 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
209 source_x
= source_x_q16
>>16;
210 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
211 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
212 source_x_q16
+= ctx
->source_dx_q16
;
213 u
= ctx
->u_row
[source_x
];
214 v
= ctx
->v_row
[source_x
];
215 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
220 static void ScaleYCbCr444ToRGB565_BilinearY_Row_C(
221 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
224 source_x_q16
= ctx
->source_x0_q16
;
225 for (x
= 0; x
< ctx
->width
; x
++) {
231 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
232 source_x
= source_x_q16
>>16;
233 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
234 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>16;
235 source_x_q16
+= ctx
->source_dx_q16
;
236 u
= ctx
->u_row
[source_x
];
237 v
= ctx
->v_row
[source_x
];
238 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
243 static void ScaleYCbCr42xToRGB565_Nearest_Row_C(
244 const yuv2rgb565_row_scale_nearest_ctx
*ctx
, int dither
){
251 source_x_q16
= ctx
->source_x0_q16
;
252 for (x
= 0; x
< ctx
->width
; x
++) {
253 source_x
= source_x_q16
>>16;
254 y
= ctx
->y_row
[source_x
];
255 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
256 source_x_q16
+= ctx
->source_dx_q16
;
257 u
= ctx
->u_row
[source_x
];
258 v
= ctx
->v_row
[source_x
];
259 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
264 static void ScaleYCbCr444ToRGB565_Nearest_Row_C(
265 const yuv2rgb565_row_scale_nearest_ctx
*ctx
, int dither
){
272 source_x_q16
= ctx
->source_x0_q16
;
273 for (x
= 0; x
< ctx
->width
; x
++) {
274 source_x
= source_x_q16
>>16;
275 source_x_q16
+= ctx
->source_dx_q16
;
276 y
= ctx
->y_row
[source_x
];
277 u
= ctx
->u_row
[source_x
];
278 v
= ctx
->v_row
[source_x
];
279 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
284 void ScaleYCbCrToRGB565(const uint8_t *y_buf
,
285 const uint8_t *u_buf
,
286 const uint8_t *v_buf
,
298 ScaleFilter filter
) {
303 int source_uv_xoffs_q16
;
304 int source_uv_yoffs_q16
;
312 /*We don't support negative destination rectangles (just flip the source
313 instead), and for empty ones there's nothing to do.*/
314 if (width
<= 0 || height
<= 0)
316 /*These bounds are required to avoid 16.16 fixed-point overflow.*/
317 NS_ASSERTION(source_x0
> (INT_MIN
>>16) && source_x0
< (INT_MAX
>>16),
318 "ScaleYCbCrToRGB565 source X offset out of bounds.");
319 NS_ASSERTION(source_x0
+source_width
> (INT_MIN
>>16)
320 && source_x0
+source_width
< (INT_MAX
>>16),
321 "ScaleYCbCrToRGB565 source width out of bounds.");
322 NS_ASSERTION(source_y0
> (INT_MIN
>>16) && source_y0
< (INT_MAX
>>16),
323 "ScaleYCbCrToRGB565 source Y offset out of bounds.");
324 NS_ASSERTION(source_y0
+source_height
> (INT_MIN
>>16)
325 && source_y0
+source_height
< (INT_MAX
>>16),
326 "ScaleYCbCrToRGB565 source height out of bounds.");
327 /*We require the same stride for Y' and Cb and Cr for 4:4:4 content.*/
328 NS_ASSERTION(yuv_type
!= YV24
|| y_pitch
== uv_pitch
,
329 "ScaleYCbCrToRGB565 luma stride differs from chroma for 4:4:4 content.");
330 /*We assume we can read outside the bounds of the input, because it makes
331 the code much simpler (and in practice is true: both Theora and VP8 return
332 padded reference frames).
333 In practice, we do not even _have_ the actual bounds of the source, as
334 we are passed a crop rectangle from it, and not the dimensions of the full
336 This assertion will not guarantee our out-of-bounds reads are safe, but it
337 should at least catch the simple case of passing in an unpadded buffer.*/
338 NS_ASSERTION(abs(y_pitch
) >= abs(source_width
)+16,
339 "ScaleYCbCrToRGB565 source image unpadded?");
340 /*The NEON code requires the pointers to be aligned to a 16-byte boundary at
341 the start of each row.
342 This should be true for all of our sources.
343 We could try to fix this up if it's not true by adjusting source_x0, but
344 that would require the mis-alignment to be the same for the U and V
346 NS_ASSERTION((y_pitch
&15) == 0 && (uv_pitch
&15) == 0 &&
347 ((y_buf
-(uint8_t *)nullptr)&15) == 0 &&
348 ((u_buf
-(uint8_t *)nullptr)&15) == 0 &&
349 ((v_buf
-(uint8_t *)nullptr)&15) == 0,
350 "ScaleYCbCrToRGB565 source image unaligned");
351 /*We take an area-based approach to pixel coverage to avoid shifting by small
352 amounts (or not so small, when up-scaling or down-scaling by a large
355 An illustrative example: scaling 4:2:0 up by 2, using JPEG chroma cositing^.
357 + = RGB destination locations
358 * = Y' source locations
359 - = Cb, Cr source locations
377 So, the coordinates of the upper-left + (first destination site) should
378 be (-0.25,-0.25) in the source Y' coordinate system.
379 Similarly, the coordinates should be (-0.375,-0.375) in the source Cb, Cr
381 Note that the origin and scale of these two coordinate systems is not the
384 ^JPEG cositing is required for Theora; VP8 doesn't specify cositing rules,
385 but nearly all software converters in existence (at least those that are
386 open source, and many that are not) use JPEG cositing instead of MPEG.*/
387 source_dx_q16
= (source_width
<<16) / width
;
388 source_x0_q16
= (source_x0
<<16)+(source_dx_q16
>>1)-0x8000;
389 source_dy_q16
= (source_height
<<16) / height
;
390 source_y0_q16
= (source_y0
<<16)+(source_dy_q16
>>1)-0x8000;
391 x_shift
= (yuv_type
!= YV24
);
392 y_shift
= (yuv_type
== YV12
);
393 /*These two variables hold the difference between the origins of the Y' and
394 the Cb, Cr coordinate systems, using the scale of the Y' coordinate
396 source_uv_xoffs_q16
= -(x_shift
<<15);
397 source_uv_yoffs_q16
= -(y_shift
<<15);
398 /*Compute the range of source rows we'll actually use.
399 This doesn't guarantee we won't read outside this range.*/
400 ymin
= source_height
>= 0 ? source_y0
: source_y0
+source_height
-1;
401 ymax
= source_height
>= 0 ? source_y0
+source_height
-1 : source_y0
;
402 uvmin
= ymin
>>y_shift
;
403 uvmax
= ((ymax
+1+y_shift
)>>y_shift
)-1;
404 /*Pick a dithering pattern.
405 The "&3" at the end is just in case RAND_MAX is lying.*/
406 dither
= (rand()/(RAND_MAX
>>2))&3;
407 /*Nearest-neighbor scaling.*/
408 if (filter
== FILTER_NONE
) {
409 yuv2rgb565_row_scale_nearest_ctx ctx
;
410 yuv2rgb565_row_scale_nearest_func scale_row
;
412 /*Add rounding offsets once, in advance.*/
413 source_x0_q16
+= 0x8000;
414 source_y0_q16
+= 0x8000;
415 source_uv_xoffs_q16
+= (x_shift
<<15);
416 source_uv_yoffs_q16
+= (y_shift
<<15);
417 if (yuv_type
== YV12
)
418 scale_row
= ScaleYCbCr42xToRGB565_Nearest_Row_C
;
420 scale_row
= ScaleYCbCr444ToRGB565_Nearest_Row_C
;
422 ctx
.source_x0_q16
= source_x0_q16
;
423 ctx
.source_dx_q16
= source_dx_q16
;
424 ctx
.source_uv_xoffs_q16
= source_uv_xoffs_q16
;
425 for (y
=0; y
<height
; y
++) {
427 ctx
.rgb_row
= (uint16_t *)(rgb_buf
+ y
*rgb_pitch
);
428 source_y
= source_y0_q16
>>16;
429 source_y
= clamped(source_y
, ymin
, ymax
);
430 ctx
.y_row
= y_buf
+ source_y
*y_pitch
;
431 source_y
= (source_y0_q16
+source_uv_yoffs_q16
)>>(16+y_shift
);
432 source_y
= clamped(source_y
, uvmin
, uvmax
);
433 source_y0_q16
+= source_dy_q16
;
434 ctx
.u_row
= u_buf
+ source_y
*uv_pitch
;
435 ctx
.v_row
= v_buf
+ source_y
*uv_pitch
;
436 (*scale_row
)(&ctx
, dither
);
440 /*Bilinear scaling.*/
442 yuv2rgb565_row_scale_bilinear_ctx ctx
;
443 yuv2rgb565_row_scale_bilinear_func scale_row
;
449 /*Check how close the chroma scaling is to unity.
450 If it's close enough, we can get away with nearest-neighbor chroma
451 sub-sampling, and only doing bilinear on luma.
452 If a given axis is subsampled, we use bounds on the luma step of
453 [0.67...2], which is equivalent to scaling chroma by [1...3].
454 If it's not subsampled, we use bounds of [0.5...1.33], which is
455 equivalent to scaling chroma by [0.75...2].
456 The lower bound is chosen as a trade-off between speed and how terrible
457 nearest neighbor looks when upscaling.*/
458 # define CHROMA_NEAREST_SUBSAMP_STEP_MIN 0xAAAA
459 # define CHROMA_NEAREST_NORMAL_STEP_MIN 0x8000
460 # define CHROMA_NEAREST_SUBSAMP_STEP_MAX 0x20000
461 # define CHROMA_NEAREST_NORMAL_STEP_MAX 0x15555
462 uvxscale_min
= yuv_type
!= YV24
?
463 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
464 uvxscale_max
= yuv_type
!= YV24
?
465 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
466 uvyscale_min
= yuv_type
== YV12
?
467 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
468 uvyscale_max
= yuv_type
== YV12
?
469 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
470 if (uvxscale_min
<= abs(source_dx_q16
)
471 && abs(source_dx_q16
) <= uvxscale_max
472 && uvyscale_min
<= abs(source_dy_q16
)
473 && abs(source_dy_q16
) <= uvyscale_max
) {
474 /*Add the rounding offsets now.*/
475 source_uv_xoffs_q16
+= 1<<(15+x_shift
);
476 source_uv_yoffs_q16
+= 1<<(15+y_shift
);
477 if (yuv_type
!= YV24
) {
479 //TODO: fix NEON asm for iOS
480 # if defined(MOZILLA_MAY_SUPPORT_NEON) && !defined(__APPLE__)
481 supports_neon() ? ScaleYCbCr42xToRGB565_BilinearY_Row_NEON
:
483 ScaleYCbCr42xToRGB565_BilinearY_Row_C
;
486 scale_row
= ScaleYCbCr444ToRGB565_BilinearY_Row_C
;
489 if (yuv_type
== YV12
)
490 scale_row
= ScaleYCbCr420ToRGB565_Bilinear_Row_C
;
491 else if (yuv_type
== YV16
)
492 scale_row
= ScaleYCbCr422ToRGB565_Bilinear_Row_C
;
494 scale_row
= ScaleYCbCr444ToRGB565_Bilinear_Row_C
;
497 ctx
.y_pitch
= y_pitch
;
498 ctx
.source_x0_q16
= source_x0_q16
;
499 ctx
.source_dx_q16
= source_dx_q16
;
500 ctx
.source_uv_xoffs_q16
= source_uv_xoffs_q16
;
501 ctx
.uv_pitch
= uv_pitch
;
502 for (y
=0; y
<height
; y
++) {
506 ctx
.rgb_row
= (uint16_t *)(rgb_buf
+ y
*rgb_pitch
);
507 source_y
= (source_y0_q16
+128)>>16;
508 yweight
= ((source_y0_q16
+128)>>8)&0xFF;
509 if (source_y
< ymin
) {
513 if (source_y
> ymax
) {
517 ctx
.y_row
= y_buf
+ source_y
*y_pitch
;
518 source_y
= source_y0_q16
+source_uv_yoffs_q16
+(128<<y_shift
);
519 source_y0_q16
+= source_dy_q16
;
520 uvweight
= source_y
>>(8+y_shift
)&0xFF;
521 source_y
>>= 16+y_shift
;
522 if (source_y
< uvmin
) {
526 if (source_y
> uvmax
) {
530 ctx
.u_row
= u_buf
+ source_y
*uv_pitch
;
531 ctx
.v_row
= v_buf
+ source_y
*uv_pitch
;
532 ctx
.y_yweight
= yweight
;
533 ctx
.uv_yweight
= uvweight
;
534 (*scale_row
)(&ctx
, dither
);
540 bool IsScaleYCbCrToRGB565Fast(int source_x0
,
550 if (width
<= 0 || height
<= 0)
552 # if defined(MOZILLA_MAY_SUPPORT_NEON)
553 if (filter
!= FILTER_NONE
) {
560 source_dx_q16
= (source_width
<<16) / width
;
561 source_dy_q16
= (source_height
<<16) / height
;
562 uvxscale_min
= yuv_type
!= YV24
?
563 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
564 uvxscale_max
= yuv_type
!= YV24
?
565 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
566 uvyscale_min
= yuv_type
== YV12
?
567 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
568 uvyscale_max
= yuv_type
== YV12
?
569 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
570 if (uvxscale_min
<= abs(source_dx_q16
)
571 && abs(source_dx_q16
) <= uvxscale_max
572 && uvyscale_min
<= abs(source_dy_q16
)
573 && abs(source_dy_q16
) <= uvyscale_max
) {
574 if (yuv_type
!= YV24
)
575 return supports_neon();
584 void yuv_to_rgb565_row_c(uint16
*dst
,
593 for (x
= 0; x
< pic_width
; x
++)
595 dst
[x
] = yu2rgb565(y
[pic_x
+x
],
596 u
[(pic_x
+x
)>>x_shift
],
597 v
[(pic_x
+x
)>>x_shift
],
598 2); // Disable dithering for now.
602 void ConvertYCbCrToRGB565(const uint8
* y_buf
,
617 x_shift
= yuv_type
!= YV24
;
618 y_shift
= yuv_type
== YV12
;
619 //TODO: fix NEON asm for iOS
620 # if defined(MOZILLA_MAY_SUPPORT_NEON) && !defined(__APPLE__)
621 if (yuv_type
!= YV24
&& supports_neon())
623 for (int i
= 0; i
< pic_height
; i
++) {
626 yoffs
= y_pitch
* (pic_y
+i
) + pic_x
;
627 uvoffs
= uv_pitch
* ((pic_y
+i
)>>y_shift
) + (pic_x
>>x_shift
);
628 yuv42x_to_rgb565_row_neon((uint16
*)(rgb_buf
+ rgb_pitch
* i
),
639 for (int i
= 0; i
< pic_height
; i
++) {
642 yoffs
= y_pitch
* (pic_y
+i
);
643 uvoffs
= uv_pitch
* ((pic_y
+i
)>>y_shift
);
644 yuv_to_rgb565_row_c((uint16
*)(rgb_buf
+ rgb_pitch
* i
),
655 bool IsConvertYCbCrToRGB565Fast(int pic_x
,
661 # if defined(MOZILLA_MAY_SUPPORT_NEON)
662 return (yuv_type
!= YV24
&& supports_neon());
670 } // namespace mozilla
672 #endif // HAVE_YCBCR_TO_RGB565