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