YCbCrUtils.cpp (16097B)
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 "YCbCrUtils.h" 7 8 #include "gfx2DGlue.h" 9 #include "libyuv.h" 10 #include "mozilla/EndianUtils.h" 11 #include "mozilla/gfx/Swizzle.h" 12 #include "ycbcr_to_rgb565.h" 13 #include "yuv_convert.h" 14 15 namespace mozilla { 16 namespace gfx { 17 18 static YUVType GetYUVType(const layers::PlanarYCbCrData& aData) { 19 switch (aData.mChromaSubsampling) { 20 case ChromaSubsampling::FULL: 21 return aData.mCbCrStride > 0 ? YV24 : Y8; 22 case ChromaSubsampling::HALF_WIDTH: 23 return YV16; 24 case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT: 25 return YV12; 26 } 27 MOZ_CRASH("Unknown chroma subsampling"); 28 } 29 30 void 31 GetYCbCrToRGBDestFormatAndSize(const layers::PlanarYCbCrData& aData, 32 SurfaceFormat& aSuggestedFormat, 33 IntSize& aSuggestedSize) 34 { 35 YUVType yuvtype = GetYUVType(aData); 36 37 // 'prescale' is true if the scaling is to be done as part of the 38 // YCbCr to RGB conversion rather than on the RGB data when rendered. 39 bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 && 40 aSuggestedSize != aData.mPictureRect.Size(); 41 42 if (aSuggestedFormat == SurfaceFormat::R5G6B5_UINT16) { 43 #if defined(HAVE_YCBCR_TO_RGB565) 44 if (prescale && 45 !IsScaleYCbCrToRGB565Fast(aData.mPictureRect.x, 46 aData.mPictureRect.y, 47 aData.mPictureRect.width, 48 aData.mPictureRect.height, 49 aSuggestedSize.width, 50 aSuggestedSize.height, 51 yuvtype, 52 FILTER_BILINEAR) && 53 IsConvertYCbCrToRGB565Fast(aData.mPictureRect.x, 54 aData.mPictureRect.y, 55 aData.mPictureRect.width, 56 aData.mPictureRect.height, 57 yuvtype)) { 58 prescale = false; 59 } 60 #else 61 // yuv2rgb16 function not available 62 aSuggestedFormat = SurfaceFormat::B8G8R8X8; 63 #endif 64 } 65 else if (aSuggestedFormat != SurfaceFormat::B8G8R8X8) { 66 // No other formats are currently supported. 67 aSuggestedFormat = SurfaceFormat::B8G8R8X8; 68 } 69 if (aSuggestedFormat == SurfaceFormat::B8G8R8X8) { 70 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data. 71 See bugs 639415 and 640073. */ 72 if (aData.mPictureRect.TopLeft() != IntPoint(0, 0) || yuvtype == YV24) 73 prescale = false; 74 } 75 if (!prescale) { 76 aSuggestedSize = aData.mPictureRect.Size(); 77 } 78 } 79 80 static inline void 81 ConvertYCbCr16to8Line(uint8_t* aDst, 82 int aStride, 83 const uint16_t* aSrc, 84 int aStride16, 85 int aWidth, 86 int aHeight, 87 int aBitDepth) 88 { 89 // These values from from the comment on from libyuv's Convert16To8Row_C: 90 int scale; 91 switch (aBitDepth) { 92 case 10: 93 scale = 16384; 94 break; 95 case 12: 96 scale = 4096; 97 break; 98 case 16: 99 scale = 256; 100 break; 101 default: 102 MOZ_ASSERT_UNREACHABLE("invalid bit depth value"); 103 return; 104 } 105 106 libyuv::Convert16To8Plane(aSrc, aStride16, aDst, aStride, scale, aWidth, aHeight); 107 } 108 109 struct YUV8BitData { 110 nsresult Init(const layers::PlanarYCbCrData& aData) { 111 if (aData.mColorDepth == ColorDepth::COLOR_8) { 112 mData = aData; 113 return NS_OK; 114 } 115 116 mData.mPictureRect = aData.mPictureRect; 117 118 // We align the destination stride to 32 bytes, so that libyuv can use 119 // SSE optimised code. 120 auto ySize = aData.YDataSize(); 121 auto cbcrSize = aData.CbCrDataSize(); 122 mData.mYStride = (ySize.width + 31) & ~31; 123 mData.mCbCrStride = (cbcrSize.width + 31) & ~31; 124 mData.mYUVColorSpace = aData.mYUVColorSpace; 125 mData.mColorDepth = ColorDepth::COLOR_8; 126 mData.mColorRange = aData.mColorRange; 127 mData.mChromaSubsampling = aData.mChromaSubsampling; 128 129 size_t yMemorySize = GetAlignedStride<1>(mData.mYStride, ySize.height); 130 size_t cbcrMemorySize = 131 GetAlignedStride<1>(mData.mCbCrStride, cbcrSize.height); 132 if (yMemorySize == 0) { 133 MOZ_DIAGNOSTIC_ASSERT(cbcrMemorySize == 0, 134 "CbCr without Y makes no sense"); 135 return NS_ERROR_INVALID_ARG; 136 } 137 mYChannel = MakeUnique<uint8_t[]>(yMemorySize); 138 if (!mYChannel) { 139 return NS_ERROR_OUT_OF_MEMORY; 140 } 141 142 mData.mYChannel = mYChannel.get(); 143 144 int bitDepth = BitDepthForColorDepth(aData.mColorDepth); 145 146 ConvertYCbCr16to8Line(mData.mYChannel, mData.mYStride, 147 reinterpret_cast<uint16_t*>(aData.mYChannel), 148 aData.mYStride / 2, ySize.width, ySize.height, 149 bitDepth); 150 151 if (cbcrMemorySize) { 152 mCbChannel = MakeUnique<uint8_t[]>(cbcrMemorySize); 153 if (!mCbChannel) { 154 return NS_ERROR_OUT_OF_MEMORY; 155 } 156 mCrChannel = MakeUnique<uint8_t[]>(cbcrMemorySize); 157 if (!mCrChannel) { 158 return NS_ERROR_OUT_OF_MEMORY; 159 } 160 161 mData.mCbChannel = mCbChannel.get(); 162 mData.mCrChannel = mCrChannel.get(); 163 164 ConvertYCbCr16to8Line(mData.mCbChannel, mData.mCbCrStride, 165 reinterpret_cast<uint16_t*>(aData.mCbChannel), 166 aData.mCbCrStride / 2, cbcrSize.width, 167 cbcrSize.height, bitDepth); 168 169 ConvertYCbCr16to8Line(mData.mCrChannel, mData.mCbCrStride, 170 reinterpret_cast<uint16_t*>(aData.mCrChannel), 171 aData.mCbCrStride / 2, cbcrSize.width, 172 cbcrSize.height, bitDepth); 173 } 174 if (aData.mAlpha) { 175 int32_t alphaStride8bpp = (aData.mAlpha->mSize.width + 31) & ~31; 176 size_t alphaSize = 177 GetAlignedStride<1>(alphaStride8bpp, aData.mAlpha->mSize.height); 178 mAlphaChannel = MakeUnique<uint8_t[]>(alphaSize); 179 if (!mAlphaChannel) { 180 return NS_ERROR_OUT_OF_MEMORY; 181 } 182 183 mData.mAlpha.emplace(); 184 mData.mAlpha->mPremultiplied = aData.mAlpha->mPremultiplied; 185 mData.mAlpha->mSize = aData.mAlpha->mSize; 186 mData.mAlpha->mChannel = mAlphaChannel.get(); 187 188 ConvertYCbCr16to8Line(mData.mAlpha->mChannel, alphaStride8bpp, 189 reinterpret_cast<uint16_t*>(aData.mAlpha->mChannel), 190 aData.mYStride / 2, aData.mAlpha->mSize.width, 191 aData.mAlpha->mSize.height, 192 BitDepthForColorDepth(aData.mColorDepth)); 193 } 194 return NS_OK; 195 } 196 197 const layers::PlanarYCbCrData& Get8BitData() { return mData; } 198 199 layers::PlanarYCbCrData mData; 200 UniquePtr<uint8_t[]> mYChannel; 201 UniquePtr<uint8_t[]> mCbChannel; 202 UniquePtr<uint8_t[]> mCrChannel; 203 UniquePtr<uint8_t[]> mAlphaChannel; 204 }; 205 206 static nsresult ScaleYCbCrToRGB(const layers::PlanarYCbCrData& aData, 207 const SurfaceFormat& aDestFormat, 208 const IntSize& aDestSize, 209 unsigned char* aDestBuffer, 210 int32_t aStride, 211 YUVType aYUVType) { 212 #if defined(HAVE_YCBCR_TO_RGB565) 213 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) { 214 ScaleYCbCrToRGB565(aData.mYChannel, 215 aData.mCbChannel, 216 aData.mCrChannel, 217 aDestBuffer, 218 aData.mPictureRect.x, 219 aData.mPictureRect.y, 220 aData.mPictureRect.width, 221 aData.mPictureRect.height, 222 aDestSize.width, 223 aDestSize.height, 224 aData.mYStride, 225 aData.mCbCrStride, 226 aStride, 227 aYUVType, 228 FILTER_BILINEAR); 229 return NS_OK; 230 } 231 #endif 232 return ScaleYCbCrToRGB32(aData.mYChannel, 233 aData.mCbChannel, 234 aData.mCrChannel, 235 aDestBuffer, 236 aData.mPictureRect.width, 237 aData.mPictureRect.height, 238 aDestSize.width, 239 aDestSize.height, 240 aData.mYStride, 241 aData.mCbCrStride, 242 aStride, 243 aYUVType, 244 aData.mYUVColorSpace, 245 FILTER_BILINEAR); 246 } 247 248 static nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, 249 const SurfaceFormat& aDestFormat, 250 unsigned char* aDestBuffer, 251 int32_t aStride, 252 YUVType aYUVType, 253 RGB32Type aRGB32Type) { 254 #if defined(HAVE_YCBCR_TO_RGB565) 255 if (aDestFormat == SurfaceFormat::R5G6B5_UINT16) { 256 ConvertYCbCrToRGB565(aData.mYChannel, 257 aData.mCbChannel, 258 aData.mCrChannel, 259 aDestBuffer, 260 aData.mPictureRect.x, 261 aData.mPictureRect.y, 262 aData.mPictureRect.width, 263 aData.mPictureRect.height, 264 aData.mYStride, 265 aData.mCbCrStride, 266 aStride, 267 aYUVType); 268 return NS_OK; 269 } 270 #endif 271 return ConvertYCbCrToRGB32(aData.mYChannel, 272 aData.mCbChannel, 273 aData.mCrChannel, 274 aDestBuffer, 275 aData.mPictureRect.x, 276 aData.mPictureRect.y, 277 aData.mPictureRect.width, 278 aData.mPictureRect.height, 279 aData.mYStride, 280 aData.mCbCrStride, 281 aStride, 282 aYUVType, 283 aData.mYUVColorSpace, 284 aData.mColorRange, 285 aRGB32Type); 286 } 287 288 nsresult ConvertYCbCrToRGB(const layers::PlanarYCbCrData& aData, 289 const SurfaceFormat& aDestFormat, 290 const IntSize& aDestSize, unsigned char* aDestBuffer, 291 int32_t aStride) { 292 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the 293 // luma plane is odd sized. Monochrome images have 0-sized CbCr planes 294 YUVType yuvtype = GetYUVType(aData); 295 296 YUV8BitData data; 297 nsresult result = data.Init(aData); 298 if (NS_FAILED(result)) { 299 return result; 300 } 301 const layers::PlanarYCbCrData& srcData = data.Get8BitData(); 302 303 // Convert from YCbCr to RGB now, scaling the image if needed. 304 if (aDestSize != srcData.mPictureRect.Size()) { 305 result = ScaleYCbCrToRGB(srcData, aDestFormat, aDestSize, aDestBuffer, 306 aStride, yuvtype); 307 } else { // no prescale 308 result = ConvertYCbCrToRGB(srcData, aDestFormat, aDestBuffer, aStride, 309 yuvtype, RGB32Type::ARGB); 310 } 311 if (NS_FAILED(result)) { 312 return result; 313 } 314 315 #if MOZ_BIG_ENDIAN() 316 // libyuv makes endian-correct result, which needs to be swapped to BGRX 317 if (aDestFormat != SurfaceFormat::R5G6B5_UINT16) { 318 if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8, 319 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8, 320 aDestSize)) { 321 return NS_ERROR_UNEXPECTED; 322 } 323 } 324 #endif 325 return NS_OK; 326 } 327 328 void FillAlphaToRGBA(const uint8_t* aAlpha, const int32_t aAlphaStride, 329 uint8_t* aBuffer, const int32_t aWidth, 330 const int32_t aHeight, const gfx::SurfaceFormat& aFormat) { 331 MOZ_ASSERT(aAlphaStride >= aWidth); 332 // required for SurfaceFormatBit::OS_A 333 MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 || 334 aFormat == SurfaceFormat::R8G8B8A8); 335 336 const int bpp = BytesPerPixel(aFormat); 337 const size_t rgbaStride = aWidth * bpp; 338 const uint8_t* src = aAlpha; 339 for (int32_t h = 0; h < aHeight; ++h) { 340 size_t offset = static_cast<size_t>(SurfaceFormatBit::OS_A) / 8; 341 for (int32_t w = 0; w < aWidth; ++w) { 342 aBuffer[offset] = src[w]; 343 offset += bpp; 344 } 345 src += aAlphaStride; 346 aBuffer += rgbaStride; 347 } 348 } 349 350 nsresult ConvertYCbCrToRGB32(const layers::PlanarYCbCrData& aData, 351 const SurfaceFormat& aDestFormat, 352 unsigned char* aDestBuffer, int32_t aStride, 353 PremultFunc premultiplyAlphaOp) { 354 MOZ_ASSERT(aDestFormat == SurfaceFormat::B8G8R8A8 || 355 aDestFormat == SurfaceFormat::B8G8R8X8 || 356 aDestFormat == SurfaceFormat::R8G8B8A8 || 357 aDestFormat == SurfaceFormat::R8G8B8X8); 358 359 YUVType yuvtype = GetYUVType(aData); 360 361 YUV8BitData data8pp; 362 nsresult result = data8pp.Init(aData); 363 if (NS_FAILED(result)) { 364 return result; 365 } 366 const layers::PlanarYCbCrData& data = data8pp.Get8BitData(); 367 368 // The order of SurfaceFormat's R, G, B, A is reversed compared to libyuv's 369 // order. 370 RGB32Type rgb32Type = aDestFormat == SurfaceFormat::B8G8R8A8 || 371 aDestFormat == SurfaceFormat::B8G8R8X8 372 ? RGB32Type::ARGB 373 : RGB32Type::ABGR; 374 375 result = ConvertYCbCrToRGB(data, aDestFormat, aDestBuffer, aStride, yuvtype, 376 rgb32Type); 377 if (NS_FAILED(result)) { 378 return result; 379 } 380 381 bool needAlpha = aDestFormat == SurfaceFormat::B8G8R8A8 || 382 aDestFormat == SurfaceFormat::R8G8B8A8; 383 if (data.mAlpha && needAlpha) { 384 // Alpha stride should be same as the Y stride. 385 FillAlphaToRGBA(data.mAlpha->mChannel, data.mYStride, aDestBuffer, 386 data.mPictureRect.width, aData.mPictureRect.height, 387 aDestFormat); 388 389 if (premultiplyAlphaOp) { 390 result = ToNSResult(premultiplyAlphaOp(aDestBuffer, aStride, aDestBuffer, 391 aStride, aData.mPictureRect.width, 392 aData.mPictureRect.height)); 393 if (NS_FAILED(result)) { 394 return result; 395 } 396 } 397 } 398 399 #if MOZ_BIG_ENDIAN() 400 // libyuv makes endian-correct result, which needs to be reversed to BGR* or 401 // RGB*. 402 if (!gfx::SwizzleData(aDestBuffer, aStride, gfx::SurfaceFormat::X8R8G8B8, 403 aDestBuffer, aStride, gfx::SurfaceFormat::B8G8R8X8, 404 aData.mPictureRect.Size())) { 405 return NS_ERROR_UNEXPECTED; 406 } 407 #endif 408 return NS_OK; 409 } 410 411 nsresult ConvertI420AlphaToARGB(const uint8_t* aSrcY, const uint8_t* aSrcU, 412 const uint8_t* aSrcV, const uint8_t* aSrcA, 413 int aSrcStrideYA, int aSrcStrideUV, 414 uint8_t* aDstARGB, int aDstStrideARGB, 415 int aWidth, int aHeight) { 416 nsresult result = ConvertI420AlphaToARGB32( 417 aSrcY, aSrcU, aSrcV, aSrcA, aDstARGB, aWidth, aHeight, aSrcStrideYA, 418 aSrcStrideUV, aDstStrideARGB); 419 if (NS_FAILED(result)) { 420 return result; 421 } 422 #if MOZ_BIG_ENDIAN() 423 // libyuv makes endian-correct result, which needs to be swapped to BGRA 424 if (!gfx::SwizzleData(aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::A8R8G8B8, 425 aDstARGB, aDstStrideARGB, gfx::SurfaceFormat::B8G8R8A8, 426 IntSize(aWidth, aHeight))) { 427 return NS_ERROR_UNEXPECTED; 428 } 429 #endif 430 return NS_OK; 431 } 432 433 } // namespace gfx 434 } // namespace mozilla