ImageConversion.cpp (29639B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 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 file, 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "ImageConversion.h" 7 8 #include "ImageContainer.h" 9 #include "YCbCrUtils.h" 10 #include "libyuv/convert.h" 11 #include "libyuv/convert_from_argb.h" 12 #include "libyuv/scale_argb.h" 13 #include "mozilla/PodOperations.h" 14 #include "mozilla/RefPtr.h" 15 #include "mozilla/dom/ImageBitmapBinding.h" 16 #include "mozilla/dom/ImageUtils.h" 17 #include "mozilla/gfx/Point.h" 18 #include "mozilla/gfx/Swizzle.h" 19 #include "nsThreadUtils.h" 20 #include "skia/include/core/SkBitmap.h" 21 #include "skia/include/core/SkColorSpace.h" 22 #include "skia/include/core/SkImage.h" 23 #include "skia/include/core/SkImageInfo.h" 24 25 using mozilla::ImageFormat; 26 using mozilla::dom::ImageBitmapFormat; 27 using mozilla::dom::ImageUtils; 28 using mozilla::gfx::DataSourceSurface; 29 using mozilla::gfx::IntSize; 30 using mozilla::gfx::SourceSurface; 31 using mozilla::gfx::SurfaceFormat; 32 using mozilla::layers::Image; 33 using mozilla::layers::PlanarYCbCrData; 34 using mozilla::layers::PlanarYCbCrImage; 35 36 static const PlanarYCbCrData* GetPlanarYCbCrData(Image* aImage) { 37 switch (aImage->GetFormat()) { 38 case ImageFormat::PLANAR_YCBCR: 39 return aImage->AsPlanarYCbCrImage()->GetData(); 40 case ImageFormat::NV_IMAGE: 41 return aImage->AsNVImage()->GetData(); 42 default: 43 return nullptr; 44 } 45 } 46 47 static nsresult MapRv(int aRv) { 48 // Docs for libyuv::ConvertToI420 say: 49 // Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. 50 switch (aRv) { 51 case 0: 52 return NS_OK; 53 case -1: 54 return NS_ERROR_INVALID_ARG; 55 default: 56 return NS_ERROR_FAILURE; 57 } 58 } 59 60 namespace mozilla { 61 62 already_AddRefed<SourceSurface> GetSourceSurface(Image* aImage) { 63 if (!aImage->AsGLImage() || NS_IsMainThread()) { 64 return aImage->GetAsSourceSurface(); 65 } 66 67 // GLImage::GetAsSourceSurface() only supports main thread 68 RefPtr<SourceSurface> surf; 69 NS_DispatchAndSpinEventLoopUntilComplete( 70 "ImageToI420::GLImage::GetSourceSurface"_ns, 71 mozilla::GetMainThreadSerialEventTarget(), 72 NS_NewRunnableFunction( 73 "ImageToI420::GLImage::GetSourceSurface", 74 [&aImage, &surf]() { surf = aImage->GetAsSourceSurface(); })); 75 76 return surf.forget(); 77 } 78 79 nsresult ConvertToI420(Image* aImage, uint8_t* aDestY, int aDestStrideY, 80 uint8_t* aDestU, int aDestStrideU, uint8_t* aDestV, 81 int aDestStrideV, const IntSize& aDestSize) { 82 if (!aImage->IsValid()) { 83 return NS_ERROR_INVALID_ARG; 84 } 85 86 const IntSize imageSize = aImage->GetSize(); 87 auto srcPixelCount = CheckedInt<int32_t>(imageSize.width) * imageSize.height; 88 auto dstPixelCount = CheckedInt<int32_t>(aDestSize.width) * aDestSize.height; 89 if (!srcPixelCount.isValid() || !dstPixelCount.isValid()) { 90 MOZ_ASSERT_UNREACHABLE("Bad input or output sizes"); 91 return NS_ERROR_INVALID_ARG; 92 } 93 94 // If we are downscaling, we prefer an early scale. If we are upscaling, we 95 // prefer a late scale. This minimizes the number of pixel manipulations. 96 // Depending on the input format, we may be forced to do a late scale after 97 // conversion to I420, because we don't support scaling the input format. 98 const bool needsScale = imageSize != aDestSize; 99 bool earlyScale = srcPixelCount.value() > dstPixelCount.value(); 100 101 Maybe<DataSourceSurface::ScopedMap> surfaceMap; 102 SurfaceFormat surfaceFormat = SurfaceFormat::UNKNOWN; 103 104 const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage); 105 Maybe<dom::ImageBitmapFormat> format; 106 if (data) { 107 const ImageUtils imageUtils(aImage); 108 format = imageUtils.GetFormat(); 109 if (format.isNothing()) { 110 MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); 111 return NS_ERROR_NOT_IMPLEMENTED; 112 } 113 114 switch (format.value()) { 115 case ImageBitmapFormat::YUV420P: 116 // Since the input and output formats match, we can copy or scale 117 // directly to the output buffer. 118 if (needsScale) { 119 return MapRv(libyuv::I420Scale( 120 data->mYChannel, data->mYStride, data->mCbChannel, 121 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, 122 imageSize.width, imageSize.height, aDestY, aDestStrideY, aDestU, 123 aDestStrideU, aDestV, aDestStrideV, aDestSize.width, 124 aDestSize.height, libyuv::FilterMode::kFilterBox)); 125 } 126 return MapRv(libyuv::I420ToI420( 127 data->mYChannel, data->mYStride, data->mCbChannel, 128 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY, 129 aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV, 130 aDestSize.width, aDestSize.height)); 131 case ImageBitmapFormat::YUV422P: 132 if (!needsScale) { 133 return MapRv(libyuv::I422ToI420( 134 data->mYChannel, data->mYStride, data->mCbChannel, 135 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY, 136 aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV, 137 aDestSize.width, aDestSize.height)); 138 } 139 break; 140 case ImageBitmapFormat::YUV444P: 141 if (!needsScale) { 142 return MapRv(libyuv::I444ToI420( 143 data->mYChannel, data->mYStride, data->mCbChannel, 144 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, aDestY, 145 aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV, 146 aDestSize.width, aDestSize.height)); 147 } 148 break; 149 case ImageBitmapFormat::YUV420SP_NV12: 150 if (!needsScale) { 151 return MapRv(libyuv::NV12ToI420( 152 data->mYChannel, data->mYStride, data->mCbChannel, 153 data->mCbCrStride, aDestY, aDestStrideY, aDestU, aDestStrideU, 154 aDestV, aDestStrideV, aDestSize.width, aDestSize.height)); 155 } 156 break; 157 case ImageBitmapFormat::YUV420SP_NV21: 158 if (!needsScale) { 159 return MapRv(libyuv::NV21ToI420( 160 data->mYChannel, data->mYStride, data->mCrChannel, 161 data->mCbCrStride, aDestY, aDestStrideY, aDestU, aDestStrideU, 162 aDestV, aDestStrideV, aDestSize.width, aDestSize.height)); 163 } 164 earlyScale = false; 165 break; 166 default: 167 MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); 168 return NS_ERROR_NOT_IMPLEMENTED; 169 } 170 } else { 171 RefPtr<SourceSurface> surface = GetSourceSurface(aImage); 172 if (!surface) { 173 return NS_ERROR_FAILURE; 174 } 175 176 RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); 177 if (!dataSurface) { 178 return NS_ERROR_FAILURE; 179 } 180 181 surfaceMap.emplace(dataSurface, DataSourceSurface::READ); 182 if (!surfaceMap->IsMapped()) { 183 return NS_ERROR_FAILURE; 184 } 185 186 surfaceFormat = dataSurface->GetFormat(); 187 switch (surfaceFormat) { 188 case SurfaceFormat::B8G8R8A8: 189 case SurfaceFormat::B8G8R8X8: 190 if (!needsScale) { 191 return MapRv( 192 libyuv::ARGBToI420(static_cast<uint8_t*>(surfaceMap->GetData()), 193 surfaceMap->GetStride(), aDestY, aDestStrideY, 194 aDestU, aDestStrideU, aDestV, aDestStrideV, 195 aDestSize.width, aDestSize.height)); 196 } 197 break; 198 case SurfaceFormat::R8G8B8A8: 199 case SurfaceFormat::R8G8B8X8: 200 if (!needsScale) { 201 return MapRv( 202 libyuv::ABGRToI420(static_cast<uint8_t*>(surfaceMap->GetData()), 203 surfaceMap->GetStride(), aDestY, aDestStrideY, 204 aDestU, aDestStrideU, aDestV, aDestStrideV, 205 aDestSize.width, aDestSize.height)); 206 } 207 break; 208 case SurfaceFormat::R5G6B5_UINT16: 209 if (!needsScale) { 210 return MapRv(libyuv::RGB565ToI420( 211 static_cast<uint8_t*>(surfaceMap->GetData()), 212 surfaceMap->GetStride(), aDestY, aDestStrideY, aDestU, 213 aDestStrideU, aDestV, aDestStrideV, aDestSize.width, 214 aDestSize.height)); 215 } 216 earlyScale = false; 217 break; 218 default: 219 MOZ_ASSERT_UNREACHABLE("Surface format conversion not implemented"); 220 return NS_ERROR_NOT_IMPLEMENTED; 221 } 222 } 223 224 MOZ_DIAGNOSTIC_ASSERT(needsScale); 225 226 // We have to scale, and we are unable to scale directly, so we need a 227 // temporary buffer to hold the scaled result in the input format, or the 228 // unscaled result in the output format. 229 IntSize tempBufSize; 230 IntSize tempBufCbCrSize; 231 if (earlyScale) { 232 // Early scaling means we are scaling from the input buffer to a temporary 233 // buffer of the same format. 234 tempBufSize = aDestSize; 235 if (data) { 236 tempBufCbCrSize = gfx::ChromaSize(tempBufSize, data->mChromaSubsampling); 237 } 238 } else { 239 // Late scaling means we are scaling from a temporary I420 buffer to the 240 // destination I420 buffer. 241 tempBufSize = imageSize; 242 tempBufCbCrSize = gfx::ChromaSize( 243 tempBufSize, gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT); 244 } 245 246 MOZ_ASSERT(!tempBufSize.IsEmpty()); 247 248 // Make sure we can allocate the temporary buffer. 249 gfx::AlignedArray<uint8_t> tempBuf; 250 uint8_t* tempBufY = nullptr; 251 uint8_t* tempBufU = nullptr; 252 uint8_t* tempBufV = nullptr; 253 int32_t tempRgbStride = 0; 254 if (!tempBufCbCrSize.IsEmpty()) { 255 // Our temporary buffer is represented as a YUV format. 256 auto tempBufYLen = 257 CheckedInt<size_t>(tempBufSize.width) * tempBufSize.height; 258 auto tempBufCbCrLen = 259 CheckedInt<size_t>(tempBufCbCrSize.width) * tempBufCbCrSize.height; 260 auto tempBufLen = tempBufYLen + 2 * tempBufCbCrLen; 261 if (!tempBufLen.isValid()) { 262 MOZ_ASSERT_UNREACHABLE("Bad buffer size!"); 263 return NS_ERROR_FAILURE; 264 } 265 266 tempBuf.Realloc(tempBufLen.value()); 267 if (!tempBuf) { 268 return NS_ERROR_OUT_OF_MEMORY; 269 } 270 271 tempBufY = tempBuf; 272 tempBufU = tempBufY + tempBufYLen.value(); 273 tempBufV = tempBufU + tempBufCbCrLen.value(); 274 } else { 275 // The temporary buffer is represented as a RGBA/BGRA format. 276 auto tempStride = CheckedInt<int32_t>(tempBufSize.width) * 4; 277 auto tempBufLen = tempStride * tempBufSize.height; 278 if (!tempStride.isValid() || !tempBufLen.isValid()) { 279 MOZ_ASSERT_UNREACHABLE("Bad buffer size!"); 280 return NS_ERROR_FAILURE; 281 } 282 283 tempBuf.Realloc(tempBufLen.value()); 284 if (!tempBuf) { 285 return NS_ERROR_OUT_OF_MEMORY; 286 } 287 288 tempRgbStride = tempStride.value(); 289 } 290 291 nsresult rv; 292 if (!earlyScale) { 293 // First convert whatever the input format is to I420 into the temp buffer. 294 if (data) { 295 switch (format.value()) { 296 case ImageBitmapFormat::YUV422P: 297 rv = MapRv(libyuv::I422ToI420( 298 data->mYChannel, data->mYStride, data->mCbChannel, 299 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, tempBufY, 300 tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV, 301 tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height)); 302 break; 303 case ImageBitmapFormat::YUV444P: 304 rv = MapRv(libyuv::I444ToI420( 305 data->mYChannel, data->mYStride, data->mCbChannel, 306 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, tempBufY, 307 tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV, 308 tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height)); 309 break; 310 case ImageBitmapFormat::YUV420SP_NV12: 311 rv = MapRv(libyuv::NV12ToI420( 312 data->mYChannel, data->mYStride, data->mCbChannel, 313 data->mCbCrStride, tempBufY, tempBufSize.width, tempBufU, 314 tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 315 tempBufSize.width, tempBufSize.height)); 316 break; 317 case ImageBitmapFormat::YUV420SP_NV21: 318 rv = MapRv(libyuv::NV21ToI420( 319 data->mYChannel, data->mYStride, data->mCrChannel, 320 data->mCbCrStride, tempBufY, tempBufSize.width, tempBufU, 321 tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 322 tempBufSize.width, tempBufSize.height)); 323 break; 324 default: 325 MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); 326 return NS_ERROR_UNEXPECTED; 327 } 328 } else { 329 switch (surfaceFormat) { 330 case SurfaceFormat::B8G8R8A8: 331 case SurfaceFormat::B8G8R8X8: 332 rv = MapRv(libyuv::ARGBToI420( 333 static_cast<uint8_t*>(surfaceMap->GetData()), 334 surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU, 335 tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 336 tempBufSize.width, tempBufSize.height)); 337 break; 338 case SurfaceFormat::R8G8B8A8: 339 case SurfaceFormat::R8G8B8X8: 340 rv = MapRv(libyuv::ABGRToI420( 341 static_cast<uint8_t*>(surfaceMap->GetData()), 342 surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU, 343 tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 344 tempBufSize.width, tempBufSize.height)); 345 break; 346 case SurfaceFormat::R5G6B5_UINT16: 347 rv = MapRv(libyuv::RGB565ToI420( 348 static_cast<uint8_t*>(surfaceMap->GetData()), 349 surfaceMap->GetStride(), tempBufY, tempBufSize.width, tempBufU, 350 tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 351 tempBufSize.width, tempBufSize.height)); 352 break; 353 default: 354 MOZ_ASSERT_UNREACHABLE("Surface format conversion not implemented"); 355 return NS_ERROR_NOT_IMPLEMENTED; 356 } 357 } 358 359 if (NS_FAILED(rv)) { 360 return rv; 361 } 362 363 // Now do the scale in I420 to the output buffer. 364 return MapRv(libyuv::I420Scale( 365 tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width, tempBufV, 366 tempBufCbCrSize.width, tempBufSize.width, tempBufSize.height, aDestY, 367 aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV, 368 aDestSize.width, aDestSize.height, libyuv::FilterMode::kFilterBox)); 369 } 370 371 if (data) { 372 // First scale in the input format to the desired size into temp buffer, and 373 // then convert that into the final I420 result. 374 switch (format.value()) { 375 case ImageBitmapFormat::YUV422P: 376 rv = MapRv(libyuv::I422Scale( 377 data->mYChannel, data->mYStride, data->mCbChannel, 378 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, 379 imageSize.width, imageSize.height, tempBufY, tempBufSize.width, 380 tempBufU, tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 381 tempBufSize.width, tempBufSize.height, 382 libyuv::FilterMode::kFilterBox)); 383 if (NS_FAILED(rv)) { 384 return rv; 385 } 386 return MapRv(libyuv::I422ToI420( 387 tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width, 388 tempBufV, tempBufCbCrSize.width, aDestY, aDestStrideY, aDestU, 389 aDestStrideU, aDestV, aDestStrideV, aDestSize.width, 390 aDestSize.height)); 391 case ImageBitmapFormat::YUV444P: 392 rv = MapRv(libyuv::I444Scale( 393 data->mYChannel, data->mYStride, data->mCbChannel, 394 data->mCbCrStride, data->mCrChannel, data->mCbCrStride, 395 imageSize.width, imageSize.height, tempBufY, tempBufSize.width, 396 tempBufU, tempBufCbCrSize.width, tempBufV, tempBufCbCrSize.width, 397 tempBufSize.width, tempBufSize.height, 398 libyuv::FilterMode::kFilterBox)); 399 if (NS_FAILED(rv)) { 400 return rv; 401 } 402 return MapRv(libyuv::I444ToI420( 403 tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width, 404 tempBufV, tempBufCbCrSize.width, aDestY, aDestStrideY, aDestU, 405 aDestStrideU, aDestV, aDestStrideV, aDestSize.width, 406 aDestSize.height)); 407 case ImageBitmapFormat::YUV420SP_NV12: 408 rv = MapRv(libyuv::NV12Scale( 409 data->mYChannel, data->mYStride, data->mCbChannel, 410 data->mCbCrStride, imageSize.width, imageSize.height, tempBufY, 411 tempBufSize.width, tempBufU, tempBufCbCrSize.width, 412 tempBufSize.width, tempBufSize.height, 413 libyuv::FilterMode::kFilterBox)); 414 if (NS_FAILED(rv)) { 415 return rv; 416 } 417 return MapRv(libyuv::NV12ToI420( 418 tempBufY, tempBufSize.width, tempBufU, tempBufCbCrSize.width, 419 aDestY, aDestStrideY, aDestU, aDestStrideU, aDestV, aDestStrideV, 420 aDestSize.width, aDestSize.height)); 421 default: 422 MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); 423 return NS_ERROR_NOT_IMPLEMENTED; 424 } 425 } 426 427 MOZ_DIAGNOSTIC_ASSERT(surfaceFormat == SurfaceFormat::B8G8R8X8 || 428 surfaceFormat == SurfaceFormat::B8G8R8A8 || 429 surfaceFormat == SurfaceFormat::R8G8B8X8 || 430 surfaceFormat == SurfaceFormat::R8G8B8A8); 431 432 // We can use the same scaling method for either BGRA or RGBA since the 433 // channel orders don't matter to the scaling algorithm. 434 rv = MapRv(libyuv::ARGBScale( 435 surfaceMap->GetData(), surfaceMap->GetStride(), imageSize.width, 436 imageSize.height, tempBuf, tempRgbStride, tempBufSize.width, 437 tempBufSize.height, libyuv::FilterMode::kFilterBox)); 438 if (NS_FAILED(rv)) { 439 return rv; 440 } 441 442 // Now convert the scale result to I420. 443 if (surfaceFormat == SurfaceFormat::B8G8R8A8 || 444 surfaceFormat == SurfaceFormat::B8G8R8X8) { 445 return MapRv(libyuv::ARGBToI420( 446 tempBuf, tempRgbStride, aDestY, aDestStrideY, aDestU, aDestStrideU, 447 aDestV, aDestStrideV, aDestSize.width, aDestSize.height)); 448 } 449 450 return MapRv(libyuv::ABGRToI420(tempBuf, tempRgbStride, aDestY, aDestStrideY, 451 aDestU, aDestStrideU, aDestV, aDestStrideV, 452 aDestSize.width, aDestSize.height)); 453 } 454 455 static int32_t CeilingOfHalf(int32_t aValue) { 456 MOZ_ASSERT(aValue >= 0); 457 return aValue / 2 + (aValue % 2); 458 } 459 460 nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY, 461 uint8_t* aDestUV, int aDestStrideUV, 462 gfx::IntSize aDestSize) { 463 if (!aImage->IsValid()) { 464 return NS_ERROR_INVALID_ARG; 465 } 466 467 if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) { 468 const ImageUtils imageUtils(aImage); 469 Maybe<dom::ImageBitmapFormat> format = imageUtils.GetFormat(); 470 if (format.isNothing()) { 471 MOZ_ASSERT_UNREACHABLE("YUV format conversion not implemented"); 472 return NS_ERROR_NOT_IMPLEMENTED; 473 } 474 475 if (format.value() != ImageBitmapFormat::YUV420P) { 476 NS_WARNING("ConvertToNV12: Convert YUV data in I420 only"); 477 return NS_ERROR_NOT_IMPLEMENTED; 478 } 479 480 PlanarYCbCrData i420Source = *data; 481 gfx::AlignedArray<uint8_t> scaledI420Buffer; 482 483 if (aDestSize != aImage->GetSize()) { 484 const int32_t halfWidth = CeilingOfHalf(aDestSize.width); 485 const uint32_t halfHeight = CeilingOfHalf(aDestSize.height); 486 487 CheckedInt<size_t> ySize(aDestSize.width); 488 ySize *= aDestSize.height; 489 490 CheckedInt<size_t> uSize(halfWidth); 491 uSize *= halfHeight; 492 493 CheckedInt<size_t> vSize(uSize); 494 495 CheckedInt<size_t> i420Size = ySize + uSize + vSize; 496 if (!i420Size.isValid()) { 497 NS_WARNING("ConvertToNV12: Destination size is too large"); 498 return NS_ERROR_INVALID_ARG; 499 } 500 501 scaledI420Buffer.Realloc(i420Size.value()); 502 if (!scaledI420Buffer) { 503 NS_WARNING( 504 "ConvertToNV12: Failed to allocate buffer for rescaled I420 image"); 505 return NS_ERROR_OUT_OF_MEMORY; 506 } 507 508 // Y plane 509 i420Source.mYChannel = scaledI420Buffer; 510 i420Source.mYStride = aDestSize.width; 511 i420Source.mYSkip = 0; 512 513 // Cb plane (aka U) 514 i420Source.mCbChannel = i420Source.mYChannel + ySize.value(); 515 i420Source.mCbSkip = 0; 516 517 // Cr plane (aka V) 518 i420Source.mCrChannel = i420Source.mCbChannel + uSize.value(); 519 i420Source.mCrSkip = 0; 520 521 i420Source.mCbCrStride = halfWidth; 522 i420Source.mChromaSubsampling = 523 gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; 524 i420Source.mPictureRect = {0, 0, aDestSize.width, aDestSize.height}; 525 526 nsresult rv = MapRv(libyuv::I420Scale( 527 data->mYChannel, data->mYStride, data->mCbChannel, data->mCbCrStride, 528 data->mCrChannel, data->mCbCrStride, aImage->GetSize().width, 529 aImage->GetSize().height, i420Source.mYChannel, i420Source.mYStride, 530 i420Source.mCbChannel, i420Source.mCbCrStride, i420Source.mCrChannel, 531 i420Source.mCbCrStride, i420Source.mPictureRect.width, 532 i420Source.mPictureRect.height, libyuv::FilterMode::kFilterBox)); 533 if (NS_FAILED(rv)) { 534 NS_WARNING("ConvertToNV12: I420Scale failed"); 535 return rv; 536 } 537 } 538 539 return MapRv(libyuv::I420ToNV12( 540 i420Source.mYChannel, i420Source.mYStride, i420Source.mCbChannel, 541 i420Source.mCbCrStride, i420Source.mCrChannel, i420Source.mCbCrStride, 542 aDestY, aDestStrideY, aDestUV, aDestStrideUV, aDestSize.width, 543 aDestSize.height)); 544 } 545 546 RefPtr<SourceSurface> surf = GetSourceSurface(aImage); 547 if (!surf) { 548 return NS_ERROR_FAILURE; 549 } 550 551 RefPtr<DataSourceSurface> data = surf->GetDataSurface(); 552 if (!data) { 553 return NS_ERROR_FAILURE; 554 } 555 556 DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); 557 if (!map.IsMapped()) { 558 return NS_ERROR_FAILURE; 559 } 560 561 if (surf->GetFormat() != SurfaceFormat::B8G8R8A8 && 562 surf->GetFormat() != SurfaceFormat::B8G8R8X8) { 563 NS_WARNING("ConvertToNV12: Convert SurfaceFormat in BGR* only"); 564 return NS_ERROR_NOT_IMPLEMENTED; 565 } 566 567 struct RgbSource { 568 uint8_t* mBuffer; 569 int32_t mStride; 570 } rgbSource = {map.GetData(), map.GetStride()}; 571 572 gfx::AlignedArray<uint8_t> scaledRGB32Buffer; 573 574 if (aDestSize != aImage->GetSize()) { 575 CheckedInt<int> rgbaStride(aDestSize.width); 576 rgbaStride *= 4; 577 if (!rgbaStride.isValid()) { 578 NS_WARNING("ConvertToNV12: Destination width is too large"); 579 return NS_ERROR_INVALID_ARG; 580 } 581 582 CheckedInt<size_t> rgbSize(rgbaStride.value()); 583 rgbSize *= aDestSize.height; 584 if (!rgbSize.isValid()) { 585 NS_WARNING("ConvertToNV12: Destination size is too large"); 586 return NS_ERROR_INVALID_ARG; 587 } 588 589 scaledRGB32Buffer.Realloc(rgbSize.value()); 590 if (!scaledRGB32Buffer) { 591 NS_WARNING( 592 "ConvertToNV12: Failed to allocate buffer for rescaled RGB32 image"); 593 return NS_ERROR_OUT_OF_MEMORY; 594 } 595 596 nsresult rv = MapRv(libyuv::ARGBScale( 597 map.GetData(), map.GetStride(), aImage->GetSize().width, 598 aImage->GetSize().height, scaledRGB32Buffer, rgbaStride.value(), 599 aDestSize.width, aDestSize.height, libyuv::FilterMode::kFilterBox)); 600 if (NS_FAILED(rv)) { 601 NS_WARNING("ConvertToNV12: ARGBScale failed"); 602 return rv; 603 } 604 605 rgbSource.mBuffer = scaledRGB32Buffer; 606 rgbSource.mStride = rgbaStride.value(); 607 } 608 609 return MapRv(libyuv::ARGBToNV12(rgbSource.mBuffer, rgbSource.mStride, aDestY, 610 aDestStrideY, aDestUV, aDestStrideUV, 611 aDestSize.width, aDestSize.height)); 612 } 613 614 static bool IsRGBX(const SurfaceFormat& aFormat) { 615 return aFormat == SurfaceFormat::B8G8R8A8 || 616 aFormat == SurfaceFormat::B8G8R8X8 || 617 aFormat == SurfaceFormat::R8G8B8A8 || 618 aFormat == SurfaceFormat::R8G8B8X8 || 619 aFormat == SurfaceFormat::X8R8G8B8 || 620 aFormat == SurfaceFormat::A8R8G8B8; 621 } 622 623 static bool HasAlpha(const SurfaceFormat& aFormat) { 624 return aFormat == SurfaceFormat::B8G8R8A8 || 625 aFormat == SurfaceFormat::R8G8B8A8 || 626 aFormat == SurfaceFormat::A8R8G8B8; 627 } 628 629 static nsresult SwapRGBA(DataSourceSurface* aSurface, 630 const SurfaceFormat& aDestFormat) { 631 if (!aSurface || !IsRGBX(aSurface->GetFormat()) || !IsRGBX(aDestFormat)) { 632 return NS_ERROR_INVALID_ARG; 633 } 634 635 if (aSurface->GetFormat() == aDestFormat) { 636 return NS_OK; 637 } 638 639 DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::READ_WRITE); 640 if (!map.IsMapped()) { 641 return NS_ERROR_FAILURE; 642 } 643 644 gfx::SwizzleData(map.GetData(), map.GetStride(), aSurface->GetFormat(), 645 map.GetData(), map.GetStride(), aDestFormat, 646 aSurface->GetSize()); 647 648 return NS_OK; 649 } 650 651 nsresult ConvertToRGBA(Image* aImage, const SurfaceFormat& aDestFormat, 652 uint8_t* aDestBuffer, int aDestStride) { 653 if (!aImage || !aImage->IsValid() || aImage->GetSize().IsEmpty() || 654 !aDestBuffer || !IsRGBX(aDestFormat) || aDestStride <= 0) { 655 return NS_ERROR_INVALID_ARG; 656 } 657 658 // Read YUV image to the given buffer in required RGBA format. 659 if (const PlanarYCbCrData* data = GetPlanarYCbCrData(aImage)) { 660 SurfaceFormat convertedFormat = aDestFormat; 661 gfx::PremultFunc premultOp = nullptr; 662 if (data->mAlpha && HasAlpha(aDestFormat)) { 663 if (aDestFormat == SurfaceFormat::A8R8G8B8) { 664 convertedFormat = SurfaceFormat::B8G8R8A8; 665 } 666 if (data->mAlpha->mPremultiplied) { 667 premultOp = libyuv::ARGBUnattenuate; 668 } 669 } else { 670 if (aDestFormat == SurfaceFormat::X8R8G8B8 || 671 aDestFormat == SurfaceFormat::A8R8G8B8) { 672 convertedFormat = SurfaceFormat::B8G8R8X8; 673 } 674 } 675 676 nsresult result = 677 ConvertYCbCrToRGB32(*data, convertedFormat, aDestBuffer, 678 AssertedCast<int32_t>(aDestStride), premultOp); 679 if (NS_FAILED(result)) { 680 return result; 681 } 682 683 if (convertedFormat == aDestFormat) { 684 return NS_OK; 685 } 686 687 // Since format of the converted data returned from ConvertYCbCrToRGB or 688 // ConvertYCbCrAToARGB is BRG* or RBG*, we need swap the RGBA channels to 689 // the required format if needed. 690 691 RefPtr<DataSourceSurface> surf = 692 gfx::Factory::CreateWrappingDataSourceSurface( 693 aDestBuffer, aDestStride, aImage->GetSize(), convertedFormat); 694 695 if (!surf) { 696 return NS_ERROR_FAILURE; 697 } 698 699 return SwapRGBA(surf.get(), aDestFormat); 700 } 701 702 // Read RGBA image to the given buffer in required RGBA format. 703 704 RefPtr<SourceSurface> surf = GetSourceSurface(aImage); 705 if (!surf) { 706 return NS_ERROR_FAILURE; 707 } 708 709 if (!IsRGBX(surf->GetFormat())) { 710 return NS_ERROR_NOT_IMPLEMENTED; 711 } 712 713 RefPtr<DataSourceSurface> src = surf->GetDataSurface(); 714 if (!src) { 715 return NS_ERROR_FAILURE; 716 } 717 718 DataSourceSurface::ScopedMap srcMap(src, DataSourceSurface::READ); 719 if (!srcMap.IsMapped()) { 720 return NS_ERROR_FAILURE; 721 } 722 723 RefPtr<DataSourceSurface> dest = 724 gfx::Factory::CreateWrappingDataSourceSurface( 725 aDestBuffer, aDestStride, aImage->GetSize(), aDestFormat); 726 727 if (!dest) { 728 return NS_ERROR_FAILURE; 729 } 730 731 DataSourceSurface::ScopedMap destMap(dest, gfx::DataSourceSurface::WRITE); 732 if (!destMap.IsMapped()) { 733 return NS_ERROR_FAILURE; 734 } 735 736 gfx::SwizzleData(srcMap.GetData(), srcMap.GetStride(), src->GetFormat(), 737 destMap.GetData(), destMap.GetStride(), dest->GetFormat(), 738 dest->GetSize()); 739 740 return NS_OK; 741 } 742 743 static SkColorType ToSkColorType(const SurfaceFormat& aFormat) { 744 switch (aFormat) { 745 case SurfaceFormat::B8G8R8A8: 746 case SurfaceFormat::B8G8R8X8: 747 return kBGRA_8888_SkColorType; 748 case SurfaceFormat::R8G8B8A8: 749 case SurfaceFormat::R8G8B8X8: 750 return kRGBA_8888_SkColorType; 751 default: 752 break; 753 } 754 return kUnknown_SkColorType; 755 } 756 757 nsresult ConvertSRGBBufferToDisplayP3(uint8_t* aSrcBuffer, 758 const SurfaceFormat& aSrcFormat, 759 uint8_t* aDestBuffer, int aWidth, 760 int aHeight) { 761 if (!aSrcBuffer || !aDestBuffer || aWidth <= 0 || aHeight <= 0 || 762 !IsRGBX(aSrcFormat)) { 763 return NS_ERROR_INVALID_ARG; 764 } 765 766 SkColorType srcColorType = ToSkColorType(aSrcFormat); 767 if (srcColorType == kUnknown_SkColorType) { 768 return NS_ERROR_NOT_IMPLEMENTED; 769 } 770 771 // TODO: Provide source's color space info to customize SkColorSpace. 772 auto srcColorSpace = SkColorSpace::MakeSRGB(); 773 SkImageInfo srcInfo = SkImageInfo::Make(aWidth, aHeight, srcColorType, 774 kUnpremul_SkAlphaType, srcColorSpace); 775 776 constexpr size_t bytesPerPixel = 4; 777 CheckedInt<size_t> rowBytes(bytesPerPixel); 778 rowBytes *= aWidth; 779 if (!rowBytes.isValid()) { 780 return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; 781 } 782 783 SkBitmap srcBitmap; 784 if (!srcBitmap.installPixels(srcInfo, aSrcBuffer, rowBytes.value())) { 785 return NS_ERROR_FAILURE; 786 } 787 788 // TODO: Provide destination's color space info to customize SkColorSpace. 789 auto destColorSpace = 790 SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3); 791 792 SkBitmap destBitmap; 793 if (!destBitmap.tryAllocPixels(srcInfo.makeColorSpace(destColorSpace))) { 794 return NS_ERROR_FAILURE; 795 } 796 797 if (!srcBitmap.readPixels(destBitmap.pixmap())) { 798 return NS_ERROR_FAILURE; 799 } 800 801 CheckedInt<size_t> size(rowBytes.value()); 802 size *= aHeight; 803 if (!size.isValid()) { 804 return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR; 805 } 806 807 PodCopy(aDestBuffer, reinterpret_cast<uint8_t*>(destBitmap.getPixels()), 808 size.value()); 809 return NS_OK; 810 } 811 812 } // namespace mozilla