1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/. */
6 #include "mozilla/EndianUtils.h"
8 #include "mozilla/gfx/Swizzle.h"
10 #include "YCbCrUtils.h"
11 #include "yuv_convert.h"
12 #include "ycbcr_to_rgb565.h"
20 static YUVType
GetYUVType(const layers::PlanarYCbCrData
& aData
) {
21 switch (aData
.mChromaSubsampling
) {
22 case ChromaSubsampling::FULL
:
23 return aData
.mCbCrStride
> 0 ? YV24
: Y8
;
24 case ChromaSubsampling::HALF_WIDTH
:
26 case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT
:
29 MOZ_CRASH("Unknown chroma subsampling");
33 GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData
& aData
,
34 SurfaceFormat
& aSuggestedFormat
,
35 IntSize
& aSuggestedSize
)
37 YUVType yuvtype
= GetYUVType(aData
);
39 // 'prescale' is true if the scaling is to be done as part of the
40 // YCbCr to RGB conversion rather than on the RGB data when rendered.
41 bool prescale
= aSuggestedSize
.width
> 0 && aSuggestedSize
.height
> 0 &&
42 aSuggestedSize
!= aData
.mPictureRect
.Size();
44 if (aSuggestedFormat
== SurfaceFormat::R5G6B5_UINT16
) {
45 #if defined(HAVE_YCBCR_TO_RGB565)
47 !IsScaleYCbCrToRGB565Fast(aData
.mPictureRect
.x
,
49 aData
.mPictureRect
.width
,
50 aData
.mPictureRect
.height
,
52 aSuggestedSize
.height
,
55 IsConvertYCbCrToRGB565Fast(aData
.mPictureRect
.x
,
57 aData
.mPictureRect
.width
,
58 aData
.mPictureRect
.height
,
63 // yuv2rgb16 function not available
64 aSuggestedFormat
= SurfaceFormat::B8G8R8X8
;
67 else if (aSuggestedFormat
!= SurfaceFormat::B8G8R8X8
) {
68 // No other formats are currently supported.
69 aSuggestedFormat
= SurfaceFormat::B8G8R8X8
;
71 if (aSuggestedFormat
== SurfaceFormat::B8G8R8X8
) {
72 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
73 See bugs 639415 and 640073. */
74 if (aData
.mPictureRect
.TopLeft() != IntPoint(0, 0) || yuvtype
== YV24
)
78 aSuggestedSize
= aData
.mPictureRect
.Size();
83 ConvertYCbCr16to8Line(uint8_t* aDst
,
91 // These values from from the comment on from libyuv's Convert16To8Row_C:
104 MOZ_ASSERT_UNREACHABLE("invalid bit depth value");
108 libyuv::Convert16To8Plane(aSrc
, aStride16
, aDst
, aStride
, scale
, aWidth
, aHeight
);
112 ConvertYCbCrToRGBInternal(const layers::PlanarYCbCrData
& aData
,
113 const SurfaceFormat
& aDestFormat
,
114 const IntSize
& aDestSize
,
115 unsigned char* aDestBuffer
,
118 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
119 // luma plane is odd sized. Monochrome images have 0-sized CbCr planes
120 YUVType yuvtype
= GetYUVType(aData
);
122 // Used if converting to 8 bits YUV.
123 UniquePtr
<uint8_t[]> yChannel
;
124 UniquePtr
<uint8_t[]> cbChannel
;
125 UniquePtr
<uint8_t[]> crChannel
;
126 layers::PlanarYCbCrData dstData
;
127 const layers::PlanarYCbCrData
& srcData
=
128 aData
.mColorDepth
== ColorDepth::COLOR_8
? aData
: dstData
;
130 if (aData
.mColorDepth
!= ColorDepth::COLOR_8
) {
131 // Convert to 8 bits data first.
132 dstData
.mPictureRect
= aData
.mPictureRect
;
133 // We align the destination stride to 32 bytes, so that libyuv can use
134 // SSE optimised code.
135 auto ySize
= aData
.YDataSize();
136 auto cbcrSize
= aData
.CbCrDataSize();
137 dstData
.mYStride
= (ySize
.width
+ 31) & ~31;
138 dstData
.mCbCrStride
= (cbcrSize
.width
+ 31) & ~31;
139 dstData
.mYUVColorSpace
= aData
.mYUVColorSpace
;
140 dstData
.mColorDepth
= ColorDepth::COLOR_8
;
141 dstData
.mColorRange
= aData
.mColorRange
;
142 dstData
.mChromaSubsampling
= aData
.mChromaSubsampling
;
144 size_t yMemorySize
= GetAlignedStride
<1>(dstData
.mYStride
, ySize
.height
);
145 size_t cbcrMemorySize
=
146 GetAlignedStride
<1>(dstData
.mCbCrStride
, cbcrSize
.height
);
147 if (yMemorySize
== 0) {
148 MOZ_DIAGNOSTIC_ASSERT(cbcrMemorySize
== 0, "CbCr without Y makes no sense");
151 yChannel
= MakeUnique
<uint8_t[]>(yMemorySize
);
153 dstData
.mYChannel
= yChannel
.get();
155 int bitDepth
= BitDepthForColorDepth(aData
.mColorDepth
);
157 ConvertYCbCr16to8Line(dstData
.mYChannel
,
159 reinterpret_cast<uint16_t*>(aData
.mYChannel
),
165 if (cbcrMemorySize
) {
166 cbChannel
= MakeUnique
<uint8_t[]>(cbcrMemorySize
);
167 crChannel
= MakeUnique
<uint8_t[]>(cbcrMemorySize
);
169 dstData
.mCbChannel
= cbChannel
.get();
170 dstData
.mCrChannel
= crChannel
.get();
172 ConvertYCbCr16to8Line(dstData
.mCbChannel
,
174 reinterpret_cast<uint16_t*>(aData
.mCbChannel
),
175 aData
.mCbCrStride
/ 2,
180 ConvertYCbCr16to8Line(dstData
.mCrChannel
,
182 reinterpret_cast<uint16_t*>(aData
.mCrChannel
),
183 aData
.mCbCrStride
/ 2,
190 // Convert from YCbCr to RGB now, scaling the image if needed.
191 if (aDestSize
!= srcData
.mPictureRect
.Size()) {
192 #if defined(HAVE_YCBCR_TO_RGB565)
193 if (aDestFormat
== SurfaceFormat::R5G6B5_UINT16
) {
194 ScaleYCbCrToRGB565(srcData
.mYChannel
,
198 srcData
.mPictureRect
.x
,
199 srcData
.mPictureRect
.y
,
200 srcData
.mPictureRect
.width
,
201 srcData
.mPictureRect
.height
,
211 ScaleYCbCrToRGB32(srcData
.mYChannel
, //
215 srcData
.mPictureRect
.width
,
216 srcData
.mPictureRect
.height
,
223 srcData
.mYUVColorSpace
,
225 } else { // no prescale
226 #if defined(HAVE_YCBCR_TO_RGB565)
227 if (aDestFormat
== SurfaceFormat::R5G6B5_UINT16
) {
228 ConvertYCbCrToRGB565(srcData
.mYChannel
,
232 srcData
.mPictureRect
.x
,
233 srcData
.mPictureRect
.y
,
234 srcData
.mPictureRect
.width
,
235 srcData
.mPictureRect
.height
,
240 } else // aDestFormat != SurfaceFormat::R5G6B5_UINT16
242 ConvertYCbCrToRGB32(srcData
.mYChannel
, //
246 srcData
.mPictureRect
.x
,
247 srcData
.mPictureRect
.y
,
248 srcData
.mPictureRect
.width
,
249 srcData
.mPictureRect
.height
,
254 srcData
.mYUVColorSpace
,
255 srcData
.mColorRange
);
259 void ConvertYCbCrToRGB(const layers::PlanarYCbCrData
& aData
,
260 const SurfaceFormat
& aDestFormat
,
261 const IntSize
& aDestSize
, unsigned char* aDestBuffer
,
263 ConvertYCbCrToRGBInternal(aData
, aDestFormat
, aDestSize
, aDestBuffer
,
266 // libyuv makes endian-correct result, which needs to be swapped to BGRX
267 if (aDestFormat
!= SurfaceFormat::R5G6B5_UINT16
) {
268 gfx::SwizzleData(aDestBuffer
, aStride
, gfx::SurfaceFormat::X8R8G8B8
,
269 aDestBuffer
, aStride
, gfx::SurfaceFormat::B8G8R8X8
,
275 void FillAlphaToRGBA(const uint8_t* aAlpha
, const int32_t aAlphaStride
,
276 uint8_t* aBuffer
, const int32_t aWidth
,
277 const int32_t aHeight
, const gfx::SurfaceFormat
& aFormat
) {
278 MOZ_ASSERT(aAlphaStride
>= aWidth
);
279 MOZ_ASSERT(aFormat
==
280 SurfaceFormat::B8G8R8A8
); // required for SurfaceFormatBit::OS_A
282 const int bpp
= BytesPerPixel(aFormat
);
283 const size_t rgbaStride
= aWidth
* bpp
;
284 const uint8_t* src
= aAlpha
;
285 for (int32_t h
= 0; h
< aHeight
; ++h
) {
286 size_t offset
= static_cast<size_t>(SurfaceFormatBit::OS_A
) / 8;
287 for (int32_t w
= 0; w
< aWidth
; ++w
) {
288 aBuffer
[offset
] = src
[w
];
292 aBuffer
+= rgbaStride
;
296 void ConvertYCbCrAToARGB(const layers::PlanarYCbCrData
& aYCbCr
,
297 const layers::PlanarAlphaData
& aAlpha
,
298 const SurfaceFormat
& aDestFormat
,
299 const IntSize
& aDestSize
, unsigned char* aDestBuffer
,
300 int32_t aStride
, PremultFunc premultiplyAlphaOp
) {
301 // libyuv makes endian-correct result, so the format needs to be B8G8R8A8.
302 MOZ_ASSERT(aDestFormat
== SurfaceFormat::B8G8R8A8
);
303 MOZ_ASSERT(aAlpha
.mSize
== aYCbCr
.YDataSize());
305 // libyuv has libyuv::I420AlphaToARGB, but lacks support for 422 and 444.
306 // Until that's added, we'll rely on our own code to handle this more
307 // generally, rather than have a special case and more redundant code.
309 UniquePtr
<uint8_t[]> alphaChannel
;
310 int32_t alphaStride8bpp
= 0;
311 uint8_t* alphaChannel8bpp
= nullptr;
313 // This function converts non-8-bpc images to 8-bpc. (Bug 1682322)
314 ConvertYCbCrToRGBInternal(aYCbCr
, aDestFormat
, aDestSize
, aDestBuffer
,
317 if (aYCbCr
.mColorDepth
!= ColorDepth::COLOR_8
) {
318 // These two lines are borrowed from ConvertYCbCrToRGBInternal, since
319 // there's not a very elegant way of sharing the logic that I can see
320 alphaStride8bpp
= (aAlpha
.mSize
.width
+ 31) & ~31;
322 GetAlignedStride
<1>(alphaStride8bpp
, aAlpha
.mSize
.height
);
324 alphaChannel
= MakeUnique
<uint8_t[]>(alphaSize
);
326 ConvertYCbCr16to8Line(alphaChannel
.get(), alphaStride8bpp
,
327 reinterpret_cast<uint16_t*>(aAlpha
.mChannel
),
328 aYCbCr
.mYStride
/ 2, aAlpha
.mSize
.width
,
330 BitDepthForColorDepth(aYCbCr
.mColorDepth
));
332 alphaChannel8bpp
= alphaChannel
.get();
334 alphaStride8bpp
= aYCbCr
.mYStride
;
335 alphaChannel8bpp
= aAlpha
.mChannel
;
338 MOZ_ASSERT(alphaStride8bpp
!= 0);
339 MOZ_ASSERT(alphaChannel8bpp
);
341 FillAlphaToRGBA(alphaChannel8bpp
, alphaStride8bpp
, aDestBuffer
,
342 aYCbCr
.mPictureRect
.width
, aYCbCr
.mPictureRect
.height
, aDestFormat
);
344 if (premultiplyAlphaOp
) {
346 premultiplyAlphaOp(aDestBuffer
, aStride
, aDestBuffer
, aStride
,
347 aYCbCr
.mPictureRect
.width
, aYCbCr
.mPictureRect
.height
);
352 // libyuv makes endian-correct result, which needs to be swapped to BGRA
353 gfx::SwizzleData(aDestBuffer
, aStride
, gfx::SurfaceFormat::A8R8G8B8
,
354 aDestBuffer
, aStride
, gfx::SurfaceFormat::B8G8R8A8
,
355 aYCbCr
.mPictureRect
.Size());
360 ConvertI420AlphaToARGB(const uint8_t* aSrcY
,
361 const uint8_t* aSrcU
,
362 const uint8_t* aSrcV
,
363 const uint8_t* aSrcA
,
364 int aSrcStrideYA
, int aSrcStrideUV
,
365 uint8_t* aDstARGB
, int aDstStrideARGB
,
366 int aWidth
, int aHeight
) {
368 ConvertI420AlphaToARGB32(aSrcY
,
379 // libyuv makes endian-correct result, which needs to be swapped to BGRA
380 gfx::SwizzleData(aDstARGB
, aDstStrideARGB
, gfx::SurfaceFormat::A8R8G8B8
,
381 aDstARGB
, aDstStrideARGB
, gfx::SurfaceFormat::B8G8R8A8
,
382 IntSize(aWidth
, aHeight
));
387 } // namespace mozilla