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/. */
5
6 #include "mozilla/EndianUtils.h"
7 #include "gfx2DGlue.h"
8 #include "mozilla/gfx/Swizzle.h"
9
10 #include "YCbCrUtils.h"
11 #include "yuv_convert.h"
12 #include "ycbcr_to_rgb565.h"
13 #include "libyuv.h"
14
15 namespace mozilla {
16 namespace gfx {
17
18 // clang-format off
19
20 void
GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData & aData,SurfaceFormat & aSuggestedFormat,IntSize & aSuggestedSize)21 GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData,
22 SurfaceFormat& aSuggestedFormat,
23 IntSize& aSuggestedSize)
24 {
25 YUVType yuvtype =
26 TypeFromSize(aData.mYSize.width,
27 aData.mYSize.height,
28 aData.mCbCrSize.width,
29 aData.mCbCrSize.height);
30
31 // 'prescale' is true if the scaling is to be done as part of the
32 // YCbCr to RGB conversion rather than on the RGB data when rendered.
33 bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
34 aSuggestedSize != aData.mPicSize;
35
36 if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) {
37 #if defined(HAVE_YCBCR_TO_RGB565)
38 if (prescale &&
39 !IsScaleYCbCrToRGB565Fast(aData.mPicX,
40 aData.mPicY,
41 aData.mPicSize.width,
42 aData.mPicSize.height,
43 aSuggestedSize.width,
44 aSuggestedSize.height,
45 yuvtype,
46 FILTER_BILINEAR) &&
47 IsConvertYCbCrToRGB565Fast(aData.mPicX,
48 aData.mPicY,
49 aData.mPicSize.width,
50 aData.mPicSize.height,
51 yuvtype)) {
52 prescale = false;
53 }
54 #else
55 // yuv2rgb16 function not available
56 aSuggestedFormat = SurfaceFormat::B8G8R8X8;
57 #endif
58 }
59 else if (aSuggestedFormat != SurfaceFormat::B8G8R8X8) {
60 // No other formats are currently supported.
61 aSuggestedFormat = SurfaceFormat::B8G8R8X8;
62 }
63 if (aSuggestedFormat == SurfaceFormat::B8G8R8X8) {
64 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
65 See bugs 639415 and 640073. */
66 if (aData.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24)
67 prescale = false;
68 }
69 if (!prescale) {
70 aSuggestedSize = aData.mPicSize;
71 }
72 }
73
74 static inline void
ConvertYCbCr16to8Line(uint8_t * aDst,int aStride,const uint16_t * aSrc,int aStride16,int aWidth,int aHeight,int aBitDepth)75 ConvertYCbCr16to8Line(uint8_t* aDst,
76 int aStride,
77 const uint16_t* aSrc,
78 int aStride16,
79 int aWidth,
80 int aHeight,
81 int aBitDepth)
82 {
83 // These values from from the comment on from libyuv's Convert16To8Row_C:
84 int scale;
85 switch (aBitDepth) {
86 case 10:
87 scale = 16384;
88 break;
89 case 12:
90 scale = 4096;
91 break;
92 case 16:
93 scale = 256;
94 break;
95 default:
96 MOZ_ASSERT_UNREACHABLE("invalid bit depth value");
97 return;
98 }
99
100 libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight);
101 }
102
103 void
ConvertYCbCrToRGBInternal(const layers::PlanarYCbCrData & aData,const SurfaceFormat & aDestFormat,const IntSize & aDestSize,unsigned char * aDestBuffer,int32_t aStride)104 ConvertYCbCrToRGBInternal(const layers::PlanarYCbCrData& aData,
105 const SurfaceFormat& aDestFormat,
106 const IntSize& aDestSize,
107 unsigned char* aDestBuffer,
108 int32_t aStride)
109 {
110 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
111 // luma plane is odd sized. Monochrome images have 0-sized CbCr planes
112 MOZ_ASSERT(aData.mCbCrSize.width == aData.mYSize.width ||
113 aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1 ||
114 aData.mCbCrSize.width == 0);
115 MOZ_ASSERT(aData.mCbCrSize.height == aData.mYSize.height ||
116 aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1 ||
117 aData.mCbCrSize.height == 0);
118
119 // Used if converting to 8 bits YUV.
120 UniquePtr<uint8_t[]> yChannel;
121 UniquePtr<uint8_t[]> cbChannel;
122 UniquePtr<uint8_t[]> crChannel;
123 layers::PlanarYCbCrData dstData;
124 const layers::PlanarYCbCrData& srcData =
125 aData.mColorDepth == ColorDepth::COLOR_8 ? aData : dstData;
126
127 if (aData.mColorDepth != ColorDepth::COLOR_8) {
128 // Convert to 8 bits data first.
129 dstData.mPicSize = aData.mPicSize;
130 dstData.mPicX = aData.mPicX;
131 dstData.mPicY = aData.mPicY;
132 dstData.mYSize = aData.mYSize;
133 // We align the destination stride to 32 bytes, so that libyuv can use
134 // SSE optimised code.
135 dstData.mYStride = (aData.mYSize.width + 31) & ~31;
136 dstData.mCbCrSize = aData.mCbCrSize;
137 dstData.mCbCrStride = (aData.mCbCrSize.width + 31) & ~31;
138 dstData.mYUVColorSpace = aData.mYUVColorSpace;
139 dstData.mColorDepth = ColorDepth::COLOR_8;
140 dstData.mColorRange = aData.mColorRange;
141
142 size_t ySize = GetAlignedStride<1>(dstData.mYStride, aData.mYSize.height);
143 size_t cbcrSize =
144 GetAlignedStride<1>(dstData.mCbCrStride, aData.mCbCrSize.height);
145 if (ySize == 0) {
146 MOZ_DIAGNOSTIC_ASSERT(cbcrSize == 0, "CbCr without Y makes no sense");
147 return;
148 }
149 yChannel = MakeUnique<uint8_t[]>(ySize);
150
151 dstData.mYChannel = yChannel.get();
152
153 int bitDepth = BitDepthForColorDepth(aData.mColorDepth);
154
155 ConvertYCbCr16to8Line(dstData.mYChannel,
156 dstData.mYStride,
157 reinterpret_cast<uint16_t*>(aData.mYChannel),
158 aData.mYStride / 2,
159 aData.mYSize.width,
160 aData.mYSize.height,
161 bitDepth);
162
163 if (cbcrSize) {
164 cbChannel = MakeUnique<uint8_t[]>(cbcrSize);
165 crChannel = MakeUnique<uint8_t[]>(cbcrSize);
166
167 dstData.mCbChannel = cbChannel.get();
168 dstData.mCrChannel = crChannel.get();
169
170 ConvertYCbCr16to8Line(dstData.mCbChannel,
171 dstData.mCbCrStride,
172 reinterpret_cast<uint16_t*>(aData.mCbChannel),
173 aData.mCbCrStride / 2,
174 aData.mCbCrSize.width,
175 aData.mCbCrSize.height,
176 bitDepth);
177
178 ConvertYCbCr16to8Line(dstData.mCrChannel,
179 dstData.mCbCrStride,
180 reinterpret_cast<uint16_t*>(aData.mCrChannel),
181 aData.mCbCrStride / 2,
182 aData.mCbCrSize.width,
183 aData.mCbCrSize.height,
184 bitDepth);
185 }
186 }
187
188 YUVType yuvtype =
189 TypeFromSize(srcData.mYSize.width,
190 srcData.mYSize.height,
191 srcData.mCbCrSize.width,
192 srcData.mCbCrSize.height);
193
194 // Convert from YCbCr to RGB now, scaling the image if needed.
195 if (aDestSize != srcData.mPicSize) {
196 #if defined(HAVE_YCBCR_TO_RGB565)
197 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
198 ScaleYCbCrToRGB565(srcData.mYChannel,
199 srcData.mCbChannel,
200 srcData.mCrChannel,
201 aDestBuffer,
202 srcData.mPicX,
203 srcData.mPicY,
204 srcData.mPicSize.width,
205 srcData.mPicSize.height,
206 aDestSize.width,
207 aDestSize.height,
208 srcData.mYStride,
209 srcData.mCbCrStride,
210 aStride,
211 yuvtype,
212 FILTER_BILINEAR);
213 } else
214 #endif
215 ScaleYCbCrToRGB32(srcData.mYChannel, //
216 srcData.mCbChannel,
217 srcData.mCrChannel,
218 aDestBuffer,
219 srcData.mPicSize.width,
220 srcData.mPicSize.height,
221 aDestSize.width,
222 aDestSize.height,
223 srcData.mYStride,
224 srcData.mCbCrStride,
225 aStride,
226 yuvtype,
227 srcData.mYUVColorSpace,
228 FILTER_BILINEAR);
229 } else { // no prescale
230 #if defined(HAVE_YCBCR_TO_RGB565)
231 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) {
232 ConvertYCbCrToRGB565(srcData.mYChannel,
233 srcData.mCbChannel,
234 srcData.mCrChannel,
235 aDestBuffer,
236 srcData.mPicX,
237 srcData.mPicY,
238 srcData.mPicSize.width,
239 srcData.mPicSize.height,
240 srcData.mYStride,
241 srcData.mCbCrStride,
242 aStride,
243 yuvtype);
244 } else // aDestFormat != SurfaceFormat::R5G6B5_UINT16
245 #endif
246 ConvertYCbCrToRGB32(srcData.mYChannel, //
247 srcData.mCbChannel,
248 srcData.mCrChannel,
249 aDestBuffer,
250 srcData.mPicX,
251 srcData.mPicY,
252 srcData.mPicSize.width,
253 srcData.mPicSize.height,
254 srcData.mYStride,
255 srcData.mCbCrStride,
256 aStride,
257 yuvtype,
258 srcData.mYUVColorSpace,
259 srcData.mColorRange);
260 }
261 }
262
ConvertYCbCrToRGB(const layers::PlanarYCbCrData & aData,const SurfaceFormat & aDestFormat,const IntSize & aDestSize,unsigned char * aDestBuffer,int32_t aStride)263 void ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData,
264 const SurfaceFormat& aDestFormat,
265 const IntSize& aDestSize, unsigned char* aDestBuffer,
266 int32_t aStride) {
267 ConvertYCbCrToRGBInternal(aData, aDestFormat, aDestSize, aDestBuffer,
268 aStride);
269 #if MOZ_BIG_ENDIAN()
270 // libyuv makes endian-correct result, which needs to be swapped to BGRX
271 if (aDestFormat != SurfaceFormat::R5G6B5_UINT16)
272 gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8,
273 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8,
274 aData.mPicSize);
275 #endif
276 }
277
FillAlphaToRGBA(const uint8_t * aAlpha,const int32_t aAlphaStride,uint8_t * aBuffer,const int32_t aWidth,const int32_t aHeight,const gfx::SurfaceFormat & aFormat)278 void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride,
279 uint8_t* aBuffer, const int32_t aWidth,
280 const int32_t aHeight, const gfx::SurfaceFormat& aFormat) {
281 MOZ_ASSERT(aAlphaStride >= aWidth);
282 MOZ_ASSERT(aFormat ==
283 SurfaceFormat::B8G8R8A8); // required for SurfaceFormatBit::OS_A
284
285 const int bpp = BytesPerPixel(aFormat);
286 const size_t rgbaStride = aWidth * bpp;
287 const uint8_t* src = aAlpha;
288 for (int32_t h = 0; h < aHeight; ++h) {
289 size_t offset = static_cast<size_t>(SurfaceFormatBit::OS_A) / 8;
290 for (int32_t w = 0; w < aWidth; ++w) {
291 aBuffer[offset] = src[w];
292 offset += bpp;
293 }
294 src += aAlphaStride;
295 aBuffer += rgbaStride;
296 }
297 }
298
ConvertYCbCrAToARGB(const layers::PlanarYCbCrAData & aData,const SurfaceFormat & aDestFormat,const IntSize & aDestSize,unsigned char * aDestBuffer,int32_t aStride,PremultFunc premultiplyAlphaOp)299 void ConvertYCbCrAToARGB(const layers::PlanarYCbCrAData& aData,
300 const SurfaceFormat& aDestFormat,
301 const IntSize& aDestSize, unsigned char* aDestBuffer,
302 int32_t aStride, PremultFunc premultiplyAlphaOp) {
303 // libyuv makes endian-correct result, so the format needs to be B8G8R8A8.
304 MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8);
305 MOZ_ASSERT(aData.mAlphaSize == aData.mYSize);
306 MOZ_ASSERT(aData.mAlphaChannel);
307
308 // libyuv has libyuv::I420AlphaToARGB, but lacks support for 422 and 444.
309 // Until that's added, we'll rely on our own code to handle this more
310 // generally, rather than have a special case and more redundant code.
311
312 UniquePtr<uint8_t[]> alphaChannel;
313 int32_t alphaStride8bpp = 0;
314 uint8_t* alphaChannel8bpp = nullptr;
315
316 // This function converts non-8-bpc images to 8-bpc. (Bug 1682322)
317 ConvertYCbCrToRGBInternal(aData, aDestFormat, aDestSize, aDestBuffer,
318 aStride);
319
320 if (aData.mColorDepth != ColorDepth::COLOR_8) {
321 // These two lines are borrowed from ConvertYCbCrToRGBInternal, since
322 // there's not a very elegant way of sharing the logic that I can see
323 alphaStride8bpp = (aData.mAlphaSize.width + 31) & ~31;
324 size_t alphaSize =
325 GetAlignedStride<1>(alphaStride8bpp, aData.mAlphaSize.height);
326
327 alphaChannel = MakeUnique<uint8_t[]>(alphaSize);
328
329 ConvertYCbCr16to8Line(alphaChannel.get(), alphaStride8bpp,
330 reinterpret_cast<uint16_t*>(aData.mAlphaChannel),
331 aData.mYStride / 2, aData.mAlphaSize.width,
332 aData.mAlphaSize.height,
333 BitDepthForColorDepth(aData.mColorDepth));
334
335 alphaChannel8bpp = alphaChannel.get();
336 } else {
337 alphaStride8bpp = aData.mYStride;
338 alphaChannel8bpp = aData.mAlphaChannel;
339 }
340
341 MOZ_ASSERT(alphaStride8bpp != 0);
342 MOZ_ASSERT(alphaChannel8bpp);
343
344 FillAlphaToRGBA(alphaChannel8bpp, alphaStride8bpp, aDestBuffer,
345 aData.mPicSize.width, aData.mPicSize.height, aDestFormat);
346
347 if (premultiplyAlphaOp) {
348 DebugOnly<int> err =
349 premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer, aStride,
350 aData.mPicSize.width, aData.mPicSize.height);
351 MOZ_ASSERT(!err);
352 }
353
354 #if MOZ_BIG_ENDIAN()
355 // libyuv makes endian-correct result, which needs to be swapped to BGRA
356 gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::A8R8G8B8,
357 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8A8,
358 aData.mPicSize);
359 #endif
360 }
361
362 void
ConvertI420AlphaToARGB(const uint8_t * aSrcY,const uint8_t * aSrcU,const uint8_t * aSrcV,const uint8_t * aSrcA,int aSrcStrideYA,int aSrcStrideUV,uint8_t * aDstARGB,int aDstStrideARGB,int aWidth,int aHeight)363 ConvertI420AlphaToARGB(const uint8_t* aSrcY,
364 const uint8_t* aSrcU,
365 const uint8_t* aSrcV,
366 const uint8_t* aSrcA,
367 int aSrcStrideYA, int aSrcStrideUV,
368 uint8_t* aDstARGB, int aDstStrideARGB,
369 int aWidth, int aHeight) {
370
371 ConvertI420AlphaToARGB32(aSrcY,
372 aSrcU,
373 aSrcV,
374 aSrcA,
375 aDstARGB,
376 aWidth,
377 aHeight,
378 aSrcStrideYA,
379 aSrcStrideUV,
380 aDstStrideARGB);
381 #if MOZ_BIG_ENDIAN()
382 // libyuv makes endian-correct result, which needs to be swapped to BGRA
383 gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8,
384 aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::B8G8R8A8,
385 IntSize(aWidth, aHeight));
386 #endif
387 }
388
389 } // namespace gfx
390 } // namespace mozilla
391