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