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