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 # if defined(MOZILLA_MAY_SUPPORT_NEON)
70 extern "C" void ScaleYCbCr42xToRGB565_BilinearY_Row_NEON(
71 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
);
73 void __attribute((noinline
)) yuv42x_to_rgb565_row_neon(uint16
*dst
,
84 /*Bilinear interpolation of a single value.
85 This uses the exact same formulas as the asm, even though it adds some extra
86 shifts that do nothing but reduce accuracy.*/
87 static int bislerp(const uint8_t *row
,
98 c
= row
[source_x
+pitch
];
99 d
= row
[source_x
+pitch
+1];
100 a
= ((a
<<8)+(c
-a
)*yweight
+128)>>8;
101 b
= ((b
<<8)+(d
-b
)*yweight
+128)>>8;
102 return ((a
<<8)+(b
-a
)*xweight
+128)>>8;
105 /*Convert a single pixel from Y'CbCr to RGB565.
106 This uses the exact same formulas as the asm, even though we could make the
107 constants a lot more accurate with 32-bit wide registers.*/
108 static uint16_t yu2rgb565(int y
, int u
, int v
, int dither
) {
109 /*This combines the constant offset that needs to be added during the Y'CbCr
110 conversion with a rounding offset that depends on the dither parameter.*/
111 static const int DITHER_BIAS
[4][3]={
112 {-14240, 8704, -17696},
113 {-14240+128,8704+64, -17696+128},
114 {-14240+256,8704+128,-17696+256},
115 {-14240+384,8704+192,-17696+384}
120 r
= clamped((74*y
+102*v
+DITHER_BIAS
[dither
][0])>>9, 0, 31);
121 g
= clamped((74*y
-25*u
-52*v
+DITHER_BIAS
[dither
][1])>>8, 0, 63);
122 b
= clamped((74*y
+129*u
+DITHER_BIAS
[dither
][2])>>9, 0, 31);
123 return (uint16_t)(r
<<11 | g
<<5 | b
);
126 static void ScaleYCbCr420ToRGB565_Bilinear_Row_C(
127 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
130 source_x_q16
= ctx
->source_x0_q16
;
131 for (x
= 0; x
< ctx
->width
; x
++) {
137 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
138 source_x
= source_x_q16
>>16;
139 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
140 xweight
= (((source_x_q16
+ctx
->source_uv_xoffs_q16
)&0x1FFFF)+256)>>9;
141 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
142 source_x_q16
+= ctx
->source_dx_q16
;
143 u
= bislerp(ctx
->u_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->uv_yweight
);
144 v
= bislerp(ctx
->v_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->uv_yweight
);
145 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
150 static void ScaleYCbCr422ToRGB565_Bilinear_Row_C(
151 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
154 source_x_q16
= ctx
->source_x0_q16
;
155 for (x
= 0; x
< ctx
->width
; x
++) {
161 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
162 source_x
= source_x_q16
>>16;
163 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
164 xweight
= (((source_x_q16
+ctx
->source_uv_xoffs_q16
)&0x1FFFF)+256)>>9;
165 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
166 source_x_q16
+= ctx
->source_dx_q16
;
167 u
= bislerp(ctx
->u_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->y_yweight
);
168 v
= bislerp(ctx
->v_row
, ctx
->uv_pitch
, source_x
, xweight
, ctx
->y_yweight
);
169 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
174 static void ScaleYCbCr444ToRGB565_Bilinear_Row_C(
175 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
178 source_x_q16
= ctx
->source_x0_q16
;
179 for (x
= 0; x
< ctx
->width
; x
++) {
185 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
186 source_x
= source_x_q16
>>16;
187 source_x_q16
+= ctx
->source_dx_q16
;
188 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
189 u
= bislerp(ctx
->u_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
190 v
= bislerp(ctx
->v_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
191 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
196 static void ScaleYCbCr42xToRGB565_BilinearY_Row_C(
197 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
200 source_x_q16
= ctx
->source_x0_q16
;
201 for (x
= 0; x
< ctx
->width
; x
++) {
207 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
208 source_x
= source_x_q16
>>16;
209 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
210 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
211 source_x_q16
+= ctx
->source_dx_q16
;
212 u
= ctx
->u_row
[source_x
];
213 v
= ctx
->v_row
[source_x
];
214 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
219 static void ScaleYCbCr444ToRGB565_BilinearY_Row_C(
220 const yuv2rgb565_row_scale_bilinear_ctx
*ctx
, int dither
){
223 source_x_q16
= ctx
->source_x0_q16
;
224 for (x
= 0; x
< ctx
->width
; x
++) {
230 xweight
= ((source_x_q16
&0xFFFF)+128)>>8;
231 source_x
= source_x_q16
>>16;
232 y
= bislerp(ctx
->y_row
, ctx
->y_pitch
, source_x
, xweight
, ctx
->y_yweight
);
233 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>16;
234 source_x_q16
+= ctx
->source_dx_q16
;
235 u
= ctx
->u_row
[source_x
];
236 v
= ctx
->v_row
[source_x
];
237 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
242 static void ScaleYCbCr42xToRGB565_Nearest_Row_C(
243 const yuv2rgb565_row_scale_nearest_ctx
*ctx
, int dither
){
250 source_x_q16
= ctx
->source_x0_q16
;
251 for (x
= 0; x
< ctx
->width
; x
++) {
252 source_x
= source_x_q16
>>16;
253 y
= ctx
->y_row
[source_x
];
254 source_x
= (source_x_q16
+ctx
->source_uv_xoffs_q16
)>>17;
255 source_x_q16
+= ctx
->source_dx_q16
;
256 u
= ctx
->u_row
[source_x
];
257 v
= ctx
->v_row
[source_x
];
258 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
263 static void ScaleYCbCr444ToRGB565_Nearest_Row_C(
264 const yuv2rgb565_row_scale_nearest_ctx
*ctx
, int dither
){
271 source_x_q16
= ctx
->source_x0_q16
;
272 for (x
= 0; x
< ctx
->width
; x
++) {
273 source_x
= source_x_q16
>>16;
274 source_x_q16
+= ctx
->source_dx_q16
;
275 y
= ctx
->y_row
[source_x
];
276 u
= ctx
->u_row
[source_x
];
277 v
= ctx
->v_row
[source_x
];
278 ctx
->rgb_row
[x
] = yu2rgb565(y
, u
, v
, dither
);
283 NS_GFX_(void) ScaleYCbCrToRGB565(const uint8_t *y_buf
,
284 const uint8_t *u_buf
,
285 const uint8_t *v_buf
,
297 ScaleFilter filter
) {
302 int source_uv_xoffs_q16
;
303 int source_uv_yoffs_q16
;
311 /*We don't support negative destination rectangles (just flip the source
312 instead), and for empty ones there's nothing to do.*/
313 if (width
<= 0 || height
<= 0)
315 /*These bounds are required to avoid 16.16 fixed-point overflow.*/
316 NS_ASSERTION(source_x0
> (INT_MIN
>>16) && source_x0
< (INT_MAX
>>16),
317 "ScaleYCbCrToRGB565 source X offset out of bounds.");
318 NS_ASSERTION(source_x0
+source_width
> (INT_MIN
>>16)
319 && source_x0
+source_width
< (INT_MAX
>>16),
320 "ScaleYCbCrToRGB565 source width out of bounds.");
321 NS_ASSERTION(source_y0
> (INT_MIN
>>16) && source_y0
< (INT_MAX
>>16),
322 "ScaleYCbCrToRGB565 source Y offset out of bounds.");
323 NS_ASSERTION(source_y0
+source_height
> (INT_MIN
>>16)
324 && source_y0
+source_height
< (INT_MAX
>>16),
325 "ScaleYCbCrToRGB565 source height out of bounds.");
326 /*We require the same stride for Y' and Cb and Cr for 4:4:4 content.*/
327 NS_ASSERTION(yuv_type
!= YV24
|| y_pitch
== uv_pitch
,
328 "ScaleYCbCrToRGB565 luma stride differs from chroma for 4:4:4 content.");
329 /*We assume we can read outside the bounds of the input, because it makes
330 the code much simpler (and in practice is true: both Theora and VP8 return
331 padded reference frames).
332 In practice, we do not even _have_ the actual bounds of the source, as
333 we are passed a crop rectangle from it, and not the dimensions of the full
335 This assertion will not guarantee our out-of-bounds reads are safe, but it
336 should at least catch the simple case of passing in an unpadded buffer.*/
337 NS_ASSERTION(abs(y_pitch
) >= abs(source_width
)+16,
338 "ScaleYCbCrToRGB565 source image unpadded?");
339 /*The NEON code requires the pointers to be aligned to a 16-byte boundary at
340 the start of each row.
341 This should be true for all of our sources.
342 We could try to fix this up if it's not true by adjusting source_x0, but
343 that would require the mis-alignment to be the same for the U and V
345 NS_ASSERTION((y_pitch
&15) == 0 && (uv_pitch
&15) == 0 &&
346 ((y_buf
-(uint8_t *)nullptr)&15) == 0 &&
347 ((u_buf
-(uint8_t *)nullptr)&15) == 0 &&
348 ((v_buf
-(uint8_t *)nullptr)&15) == 0,
349 "ScaleYCbCrToRGB565 source image unaligned");
350 /*We take an area-based approach to pixel coverage to avoid shifting by small
351 amounts (or not so small, when up-scaling or down-scaling by a large
354 An illustrative example: scaling 4:2:0 up by 2, using JPEG chroma cositing^.
356 + = RGB destination locations
357 * = Y' source locations
358 - = Cb, Cr source locations
376 So, the coordinates of the upper-left + (first destination site) should
377 be (-0.25,-0.25) in the source Y' coordinate system.
378 Similarly, the coordinates should be (-0.375,-0.375) in the source Cb, Cr
380 Note that the origin and scale of these two coordinate systems is not the
383 ^JPEG cositing is required for Theora; VP8 doesn't specify cositing rules,
384 but nearly all software converters in existence (at least those that are
385 open source, and many that are not) use JPEG cositing instead of MPEG.*/
386 source_dx_q16
= (source_width
<<16) / width
;
387 source_x0_q16
= (source_x0
<<16)+(source_dx_q16
>>1)-0x8000;
388 source_dy_q16
= (source_height
<<16) / height
;
389 source_y0_q16
= (source_y0
<<16)+(source_dy_q16
>>1)-0x8000;
390 x_shift
= (yuv_type
!= YV24
);
391 y_shift
= (yuv_type
== YV12
);
392 /*These two variables hold the difference between the origins of the Y' and
393 the Cb, Cr coordinate systems, using the scale of the Y' coordinate
395 source_uv_xoffs_q16
= -(x_shift
<<15);
396 source_uv_yoffs_q16
= -(y_shift
<<15);
397 /*Compute the range of source rows we'll actually use.
398 This doesn't guarantee we won't read outside this range.*/
399 ymin
= source_height
>= 0 ? source_y0
: source_y0
+source_height
-1;
400 ymax
= source_height
>= 0 ? source_y0
+source_height
-1 : source_y0
;
401 uvmin
= ymin
>>y_shift
;
402 uvmax
= ((ymax
+1+y_shift
)>>y_shift
)-1;
403 /*Pick a dithering pattern.
404 The "&3" at the end is just in case RAND_MAX is lying.*/
405 dither
= (rand()/(RAND_MAX
>>2))&3;
406 /*Nearest-neighbor scaling.*/
407 if (filter
== FILTER_NONE
) {
408 yuv2rgb565_row_scale_nearest_ctx ctx
;
409 yuv2rgb565_row_scale_nearest_func scale_row
;
411 /*Add rounding offsets once, in advance.*/
412 source_x0_q16
+= 0x8000;
413 source_y0_q16
+= 0x8000;
414 source_uv_xoffs_q16
+= (x_shift
<<15);
415 source_uv_yoffs_q16
+= (y_shift
<<15);
416 if (yuv_type
== YV12
)
417 scale_row
= ScaleYCbCr42xToRGB565_Nearest_Row_C
;
419 scale_row
= ScaleYCbCr444ToRGB565_Nearest_Row_C
;
421 ctx
.source_x0_q16
= source_x0_q16
;
422 ctx
.source_dx_q16
= source_dx_q16
;
423 ctx
.source_uv_xoffs_q16
= source_uv_xoffs_q16
;
424 for (y
=0; y
<height
; y
++) {
426 ctx
.rgb_row
= (uint16_t *)(rgb_buf
+ y
*rgb_pitch
);
427 source_y
= source_y0_q16
>>16;
428 source_y
= clamped(source_y
, ymin
, ymax
);
429 ctx
.y_row
= y_buf
+ source_y
*y_pitch
;
430 source_y
= (source_y0_q16
+source_uv_yoffs_q16
)>>(16+y_shift
);
431 source_y
= clamped(source_y
, uvmin
, uvmax
);
432 source_y0_q16
+= source_dy_q16
;
433 ctx
.u_row
= u_buf
+ source_y
*uv_pitch
;
434 ctx
.v_row
= v_buf
+ source_y
*uv_pitch
;
435 (*scale_row
)(&ctx
, dither
);
439 /*Bilinear scaling.*/
441 yuv2rgb565_row_scale_bilinear_ctx ctx
;
442 yuv2rgb565_row_scale_bilinear_func scale_row
;
448 /*Check how close the chroma scaling is to unity.
449 If it's close enough, we can get away with nearest-neighbor chroma
450 sub-sampling, and only doing bilinear on luma.
451 If a given axis is subsampled, we use bounds on the luma step of
452 [0.67...2], which is equivalent to scaling chroma by [1...3].
453 If it's not subsampled, we use bounds of [0.5...1.33], which is
454 equivalent to scaling chroma by [0.75...2].
455 The lower bound is chosen as a trade-off between speed and how terrible
456 nearest neighbor looks when upscaling.*/
457 # define CHROMA_NEAREST_SUBSAMP_STEP_MIN 0xAAAA
458 # define CHROMA_NEAREST_NORMAL_STEP_MIN 0x8000
459 # define CHROMA_NEAREST_SUBSAMP_STEP_MAX 0x20000
460 # define CHROMA_NEAREST_NORMAL_STEP_MAX 0x15555
461 uvxscale_min
= yuv_type
!= YV24
?
462 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
463 uvxscale_max
= yuv_type
!= YV24
?
464 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
465 uvyscale_min
= yuv_type
== YV12
?
466 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
467 uvyscale_max
= yuv_type
== YV12
?
468 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
469 if (uvxscale_min
<= abs(source_dx_q16
)
470 && abs(source_dx_q16
) <= uvxscale_max
471 && uvyscale_min
<= abs(source_dy_q16
)
472 && abs(source_dy_q16
) <= uvyscale_max
) {
473 /*Add the rounding offsets now.*/
474 source_uv_xoffs_q16
+= 1<<(15+x_shift
);
475 source_uv_yoffs_q16
+= 1<<(15+y_shift
);
476 if (yuv_type
!= YV24
) {
478 # if defined(MOZILLA_MAY_SUPPORT_NEON)
479 supports_neon() ? ScaleYCbCr42xToRGB565_BilinearY_Row_NEON
:
481 ScaleYCbCr42xToRGB565_BilinearY_Row_C
;
484 scale_row
= ScaleYCbCr444ToRGB565_BilinearY_Row_C
;
487 if (yuv_type
== YV12
)
488 scale_row
= ScaleYCbCr420ToRGB565_Bilinear_Row_C
;
489 else if (yuv_type
== YV16
)
490 scale_row
= ScaleYCbCr422ToRGB565_Bilinear_Row_C
;
492 scale_row
= ScaleYCbCr444ToRGB565_Bilinear_Row_C
;
495 ctx
.y_pitch
= y_pitch
;
496 ctx
.source_x0_q16
= source_x0_q16
;
497 ctx
.source_dx_q16
= source_dx_q16
;
498 ctx
.source_uv_xoffs_q16
= source_uv_xoffs_q16
;
499 ctx
.uv_pitch
= uv_pitch
;
500 for (y
=0; y
<height
; y
++) {
504 ctx
.rgb_row
= (uint16_t *)(rgb_buf
+ y
*rgb_pitch
);
505 source_y
= (source_y0_q16
+128)>>16;
506 yweight
= ((source_y0_q16
+128)>>8)&0xFF;
507 if (source_y
< ymin
) {
511 if (source_y
> ymax
) {
515 ctx
.y_row
= y_buf
+ source_y
*y_pitch
;
516 source_y
= source_y0_q16
+source_uv_yoffs_q16
+(128<<y_shift
);
517 source_y0_q16
+= source_dy_q16
;
518 uvweight
= source_y
>>(8+y_shift
)&0xFF;
519 source_y
>>= 16+y_shift
;
520 if (source_y
< uvmin
) {
524 if (source_y
> uvmax
) {
528 ctx
.u_row
= u_buf
+ source_y
*uv_pitch
;
529 ctx
.v_row
= v_buf
+ source_y
*uv_pitch
;
530 ctx
.y_yweight
= yweight
;
531 ctx
.uv_yweight
= uvweight
;
532 (*scale_row
)(&ctx
, dither
);
538 NS_GFX_(bool) IsScaleYCbCrToRGB565Fast(int source_x0
,
548 if (width
<= 0 || height
<= 0)
550 # if defined(MOZILLA_MAY_SUPPORT_NEON)
551 if (filter
!= FILTER_NONE
) {
558 source_dx_q16
= (source_width
<<16) / width
;
559 source_dy_q16
= (source_height
<<16) / height
;
560 uvxscale_min
= yuv_type
!= YV24
?
561 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
562 uvxscale_max
= yuv_type
!= YV24
?
563 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
564 uvyscale_min
= yuv_type
== YV12
?
565 CHROMA_NEAREST_SUBSAMP_STEP_MIN
: CHROMA_NEAREST_NORMAL_STEP_MIN
;
566 uvyscale_max
= yuv_type
== YV12
?
567 CHROMA_NEAREST_SUBSAMP_STEP_MAX
: CHROMA_NEAREST_NORMAL_STEP_MAX
;
568 if (uvxscale_min
<= abs(source_dx_q16
)
569 && abs(source_dx_q16
) <= uvxscale_max
570 && uvyscale_min
<= abs(source_dy_q16
)
571 && abs(source_dy_q16
) <= uvyscale_max
) {
572 if (yuv_type
!= YV24
)
573 return supports_neon();
582 void yuv_to_rgb565_row_c(uint16
*dst
,
591 for (x
= 0; x
< pic_width
; x
++)
593 dst
[x
] = yu2rgb565(y
[pic_x
+x
],
594 u
[(pic_x
+x
)>>x_shift
],
595 v
[(pic_x
+x
)>>x_shift
],
596 2); // Disable dithering for now.
600 NS_GFX_(void) ConvertYCbCrToRGB565(const uint8
* y_buf
,
615 x_shift
= yuv_type
!= YV24
;
616 y_shift
= yuv_type
== YV12
;
617 # ifdef MOZILLA_MAY_SUPPORT_NEON
618 if (yuv_type
!= YV24
&& supports_neon())
620 for (int i
= 0; i
< pic_height
; i
++) {
623 yoffs
= y_pitch
* (pic_y
+i
) + pic_x
;
624 uvoffs
= uv_pitch
* ((pic_y
+i
)>>y_shift
) + (pic_x
>>x_shift
);
625 yuv42x_to_rgb565_row_neon((uint16
*)(rgb_buf
+ rgb_pitch
* i
),
636 for (int i
= 0; i
< pic_height
; i
++) {
639 yoffs
= y_pitch
* (pic_y
+i
);
640 uvoffs
= uv_pitch
* ((pic_y
+i
)>>y_shift
);
641 yuv_to_rgb565_row_c((uint16
*)(rgb_buf
+ rgb_pitch
* i
),
652 NS_GFX_(bool) IsConvertYCbCrToRGB565Fast(int pic_x
,
658 # if defined(MOZILLA_MAY_SUPPORT_NEON)
659 return (yuv_type
!= YV24
&& supports_neon());
667 } // namespace mozilla
669 #endif // HAVE_YCBCR_TO_RGB565