ImageEncoder.cpp (17771B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ImageEncoder.h" 8 9 #include "YCbCrUtils.h" 10 #include "gfxUtils.h" 11 #include "mozilla/RefPtr.h" 12 #include "mozilla/SyncRunnable.h" 13 #include "mozilla/dom/CanvasRenderingContext2D.h" 14 #include "mozilla/dom/GeneratePlaceholderCanvasData.h" 15 #include "mozilla/dom/MemoryBlobImpl.h" 16 #include "mozilla/dom/OffscreenCanvasDisplayHelper.h" 17 #include "mozilla/dom/WorkerPrivate.h" 18 #include "mozilla/gfx/2D.h" 19 #include "mozilla/gfx/DataSurfaceHelpers.h" 20 #include "mozilla/layers/CanvasRenderer.h" 21 #include "nsComponentManagerUtils.h" 22 #include "nsNetUtil.h" 23 #include "nsThreadUtils.h" 24 #include "nsXPCOMCIDInternal.h" 25 26 using namespace mozilla::gfx; 27 28 namespace mozilla::dom { 29 30 // This class should be placed inside GetBRGADataSourceSurfaceSync(). However, 31 // due to B2G ICS uses old complier (C++98/03) which forbids local class as 32 // template parameter, we need to move this class outside. 33 class SurfaceHelper : public Runnable { 34 public: 35 explicit SurfaceHelper(already_AddRefed<layers::Image> aImage) 36 : Runnable("SurfaceHelper"), mImage(aImage) {} 37 38 // It retrieves a SourceSurface reference and convert color format on main 39 // thread and passes DataSourceSurface to caller thread. 40 NS_IMETHOD Run() override { 41 RefPtr<gfx::SourceSurface> surface = mImage->GetAsSourceSurface(); 42 43 if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) { 44 mDataSourceSurface = surface->GetDataSurface(); 45 } else { 46 mDataSourceSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat( 47 surface, gfx::SurfaceFormat::B8G8R8A8); 48 } 49 50 // It guarantees the reference will be released on main thread. 51 NS_ReleaseOnMainThread("SurfaceHelper::surface", surface.forget()); 52 return NS_OK; 53 } 54 55 already_AddRefed<gfx::DataSourceSurface> GetDataSurfaceSafe() { 56 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget(); 57 MOZ_ASSERT(mainTarget); 58 SyncRunnable::DispatchToThread(mainTarget, this, false); 59 60 return mDataSourceSurface.forget(); 61 } 62 63 private: 64 RefPtr<layers::Image> mImage; 65 RefPtr<gfx::DataSourceSurface> mDataSourceSurface; 66 }; 67 68 // This function returns a DataSourceSurface in B8G8R8A8 format. 69 // It uses SourceSurface to do format convert. Because most SourceSurface in 70 // image formats should be referenced or dereferenced on main thread, it uses a 71 // sync class SurfaceHelper to retrieve SourceSurface and convert to B8G8R8A8 on 72 // main thread. 73 already_AddRefed<DataSourceSurface> GetBRGADataSourceSurfaceSync( 74 already_AddRefed<layers::Image> aImage) { 75 RefPtr<SurfaceHelper> helper = new SurfaceHelper(std::move(aImage)); 76 return helper->GetDataSurfaceSafe(); 77 } 78 79 class EncodingCompleteEvent final : public DiscardableRunnable { 80 virtual ~EncodingCompleteEvent() = default; 81 82 public: 83 explicit EncodingCompleteEvent( 84 EncodeCompleteCallback* aEncodeCompleteCallback) 85 : DiscardableRunnable("EncodingCompleteEvent"), 86 mImgSize(0), 87 mImgData(nullptr), 88 mEncodeCompleteCallback(aEncodeCompleteCallback), 89 mFailed(false) { 90 if (!NS_IsMainThread() && IsCurrentThreadRunningWorker()) { 91 mCreationEventTarget = GetCurrentSerialEventTarget(); 92 } else { 93 mCreationEventTarget = GetMainThreadSerialEventTarget(); 94 } 95 } 96 97 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See 98 // bug 1535398. 99 MOZ_CAN_RUN_SCRIPT_BOUNDARY 100 NS_IMETHOD Run() override { 101 nsresult rv = NS_OK; 102 103 // We want to null out mEncodeCompleteCallback no matter what. 104 RefPtr<EncodeCompleteCallback> callback(std::move(mEncodeCompleteCallback)); 105 if (!mFailed) { 106 RefPtr<BlobImpl> blobImpl = new MemoryBlobImpl(mImgData, mImgSize, mType); 107 rv = callback->ReceiveBlobImpl(blobImpl.forget()); 108 } else { 109 rv = callback->ReceiveBlobImpl(nullptr); 110 } 111 112 return rv; 113 } 114 115 void SetMembers(void* aImgData, uint64_t aImgSize, 116 const nsAutoString& aType) { 117 mImgData = aImgData; 118 mImgSize = aImgSize; 119 mType = aType; 120 } 121 122 void SetFailed() { mFailed = true; } 123 124 nsIEventTarget* GetCreationThreadEventTarget() { 125 return mCreationEventTarget; 126 } 127 128 bool CanBeDeletedOnAnyThread() { 129 return !mEncodeCompleteCallback || 130 mEncodeCompleteCallback->CanBeDeletedOnAnyThread(); 131 } 132 133 private: 134 uint64_t mImgSize; 135 nsAutoString mType; 136 void* mImgData; 137 nsCOMPtr<nsIEventTarget> mCreationEventTarget; 138 RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback; 139 bool mFailed; 140 }; 141 142 class EncodingRunnable : public Runnable { 143 virtual ~EncodingRunnable() = default; 144 145 public: 146 NS_INLINE_DECL_REFCOUNTING_INHERITED(EncodingRunnable, Runnable) 147 148 EncodingRunnable(const nsAString& aType, const nsAString& aOptions, 149 UniquePtr<uint8_t[]> aImageBuffer, layers::Image* aImage, 150 imgIEncoder* aEncoder, 151 EncodingCompleteEvent* aEncodingCompleteEvent, 152 int32_t aFormat, const CSSIntSize aSize, 153 CanvasUtils::ImageExtraction aExtractionBehavior, 154 const nsCString& aRandomizationKey, bool aUsingCustomOptions) 155 : Runnable("EncodingRunnable"), 156 mType(aType), 157 mOptions(aOptions), 158 mImageBuffer(std::move(aImageBuffer)), 159 mImage(aImage), 160 mEncoder(aEncoder), 161 mEncodingCompleteEvent(aEncodingCompleteEvent), 162 mFormat(aFormat), 163 mSize(aSize), 164 mExtractionBehavior(aExtractionBehavior), 165 mRandomizationKey(aRandomizationKey), 166 mUsingCustomOptions(aUsingCustomOptions) {} 167 168 nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData) { 169 nsCOMPtr<nsIInputStream> stream; 170 nsresult rv = ImageEncoder::ExtractDataInternal( 171 mType, mOptions, mImageBuffer.get(), mFormat, mSize, 172 mExtractionBehavior, mRandomizationKey, mImage, nullptr, nullptr, 173 getter_AddRefs(stream), mEncoder); 174 175 // If there are unrecognized custom parse options, we should fall back to 176 // the default values for the encoder without any options at all. 177 if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) { 178 rv = ImageEncoder::ExtractDataInternal( 179 mType, u""_ns, mImageBuffer.get(), mFormat, mSize, 180 mExtractionBehavior, mRandomizationKey, mImage, nullptr, nullptr, 181 getter_AddRefs(stream), mEncoder); 182 } 183 NS_ENSURE_SUCCESS(rv, rv); 184 185 rv = NS_ReadInputStreamToBuffer(stream, aImgData, -1, aImgSize); 186 NS_ENSURE_SUCCESS(rv, rv); 187 188 return rv; 189 } 190 191 NS_IMETHOD Run() override { 192 uint64_t imgSize; 193 void* imgData = nullptr; 194 195 nsresult rv = ProcessImageData(&imgSize, &imgData); 196 if (NS_FAILED(rv)) { 197 mEncodingCompleteEvent->SetFailed(); 198 } else { 199 mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType); 200 } 201 rv = mEncodingCompleteEvent->GetCreationThreadEventTarget()->Dispatch( 202 mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL); 203 if (NS_FAILED(rv)) { 204 if (!mEncodingCompleteEvent->CanBeDeletedOnAnyThread()) { 205 // Better to leak than to crash. 206 mEncodingCompleteEvent.forget().leak(); 207 } 208 return rv; 209 } 210 211 return rv; 212 } 213 214 private: 215 nsAutoString mType; 216 nsAutoString mOptions; 217 UniquePtr<uint8_t[]> mImageBuffer; 218 RefPtr<layers::Image> mImage; 219 nsCOMPtr<imgIEncoder> mEncoder; 220 RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent; 221 int32_t mFormat; 222 const CSSIntSize mSize; 223 CanvasUtils::ImageExtraction mExtractionBehavior; 224 nsCString mRandomizationKey; 225 bool mUsingCustomOptions; 226 }; 227 228 /* static */ 229 nsresult ImageEncoder::ExtractData( 230 nsAString& aType, const nsAString& aOptions, const CSSIntSize aSize, 231 CanvasUtils::ImageExtraction aExtractionBehavior, 232 const nsCString& aRandomizationKey, 233 nsICanvasRenderingContextInternal* aContext, 234 OffscreenCanvasDisplayHelper* aOffscreenDisplay, nsIInputStream** aStream) { 235 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); 236 if (!encoder) { 237 return NS_IMAGELIB_ERROR_NO_ENCODER; 238 } 239 240 return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, 241 aExtractionBehavior, aRandomizationKey, nullptr, 242 aContext, aOffscreenDisplay, aStream, encoder); 243 } 244 245 /* static */ 246 nsresult ImageEncoder::ExtractDataFromLayersImageAsync( 247 nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions, 248 layers::Image* aImage, CanvasUtils::ImageExtraction aExtractionBehavior, 249 const nsCString& aRandomizationKey, 250 EncodeCompleteCallback* aEncodeCallback) { 251 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); 252 if (!encoder) { 253 return NS_IMAGELIB_ERROR_NO_ENCODER; 254 } 255 256 RefPtr<EncodingCompleteEvent> completeEvent = 257 new EncodingCompleteEvent(aEncodeCallback); 258 259 CSSIntSize size = CSSIntSize::FromUnknownSize(aImage->GetSize()); 260 nsCOMPtr<nsIRunnable> event = new EncodingRunnable( 261 aType, aOptions, nullptr, aImage, encoder, completeEvent, 262 imgIEncoder::INPUT_FORMAT_HOSTARGB, size, aExtractionBehavior, 263 VoidCString(), aUsingCustomOptions); 264 return NS_DispatchBackgroundTask(event.forget()); 265 } 266 267 /* static */ 268 nsresult ImageEncoder::ExtractDataAsync( 269 nsAString& aType, const nsAString& aOptions, bool aUsingCustomOptions, 270 UniquePtr<uint8_t[]> aImageBuffer, int32_t aFormat, const CSSIntSize aSize, 271 CanvasUtils::ImageExtraction aExtractionBehavior, 272 const nsCString& aRandomizationKey, 273 EncodeCompleteCallback* aEncodeCallback) { 274 nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType); 275 if (!encoder) { 276 return NS_IMAGELIB_ERROR_NO_ENCODER; 277 } 278 279 RefPtr<EncodingCompleteEvent> completeEvent = 280 new EncodingCompleteEvent(aEncodeCallback); 281 282 nsCOMPtr<nsIRunnable> event = new EncodingRunnable( 283 aType, aOptions, std::move(aImageBuffer), nullptr, encoder, completeEvent, 284 aFormat, aSize, aExtractionBehavior, aRandomizationKey, 285 aUsingCustomOptions); 286 return NS_DispatchBackgroundTask(event.forget()); 287 } 288 289 /*static*/ 290 nsresult ImageEncoder::GetInputStream(int32_t aWidth, int32_t aHeight, 291 uint8_t* aImageBuffer, int32_t aFormat, 292 imgIEncoder* aEncoder, 293 const nsAString& aEncoderOptions, 294 const nsACString& aRandomizationKey, 295 nsIInputStream** aStream) { 296 nsresult rv = aEncoder->InitFromData(aImageBuffer, aWidth * aHeight * 4, 297 aWidth, aHeight, aWidth * 4, aFormat, 298 aEncoderOptions, aRandomizationKey); 299 NS_ENSURE_SUCCESS(rv, rv); 300 301 nsCOMPtr<imgIEncoder> encoder(aEncoder); 302 encoder.forget(aStream); 303 return NS_OK; 304 } 305 306 /* static */ 307 nsresult ImageEncoder::ExtractDataInternal( 308 const nsAString& aType, const nsAString& aOptions, uint8_t* aImageBuffer, 309 int32_t aFormat, const CSSIntSize aSize, 310 CanvasUtils::ImageExtraction aExtractionBehavior, 311 const nsCString& aRandomizationKey, layers::Image* aImage, 312 nsICanvasRenderingContextInternal* aContext, 313 OffscreenCanvasDisplayHelper* aOffscreenDisplay, nsIInputStream** aStream, 314 imgIEncoder* aEncoder) { 315 if (aSize.IsEmpty()) { 316 return NS_ERROR_INVALID_ARG; 317 } 318 319 nsCOMPtr<nsIInputStream> imgStream; 320 321 // get image bytes 322 nsresult rv = NS_OK; 323 bool hasImageSource = aImageBuffer || aContext || aOffscreenDisplay || aImage; 324 bool usePlaceholder = 325 aExtractionBehavior == CanvasUtils::ImageExtraction::Placeholder; 326 if (!hasImageSource || usePlaceholder) { 327 if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) { 328 return NS_ERROR_INVALID_ARG; 329 } 330 331 // no context, so we have to encode an empty image 332 // note that if we didn't have a current context, the spec says we're 333 // supposed to just return transparent black pixels of the canvas 334 // dimensions. 335 RefPtr<DataSourceSurface> emptyCanvas = 336 Factory::CreateDataSourceSurfaceWithStride( 337 IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8, 338 4 * aSize.width, true); 339 if (NS_WARN_IF(!emptyCanvas)) { 340 return NS_ERROR_INVALID_ARG; 341 } 342 343 DataSourceSurface::MappedSurface map; 344 if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) { 345 return NS_ERROR_INVALID_ARG; 346 } 347 if (usePlaceholder) { 348 auto size = 4 * aSize.width * aSize.height; 349 auto* data = map.mData; 350 GeneratePlaceholderCanvasData(size, data); 351 } 352 rv = aEncoder->InitFromData(map.mData, aSize.width * aSize.height * 4, 353 aSize.width, aSize.height, aSize.width * 4, 354 imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions, 355 VoidCString()); 356 emptyCanvas->Unmap(); 357 if (NS_SUCCEEDED(rv)) { 358 imgStream = aEncoder; 359 } 360 } else if (aImageBuffer) { 361 if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) { 362 return NS_ERROR_INVALID_ARG; 363 } 364 365 rv = ImageEncoder::GetInputStream( 366 aSize.width, aSize.height, aImageBuffer, aFormat, aEncoder, aOptions, 367 aRandomizationKey, getter_AddRefs(imgStream)); 368 } else if (aContext) { 369 NS_ConvertUTF16toUTF8 encoderType(aType); 370 rv = aContext->GetInputStream(encoderType.get(), aOptions, 371 aExtractionBehavior, aRandomizationKey, 372 getter_AddRefs(imgStream)); 373 } else if (aOffscreenDisplay) { 374 const NS_ConvertUTF16toUTF8 encoderType(aType); 375 if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) { 376 return NS_ERROR_INVALID_ARG; 377 } 378 379 const RefPtr<SourceSurface> snapshot = 380 aOffscreenDisplay->GetSurfaceSnapshot(); 381 if (!snapshot) { 382 return NS_ERROR_OUT_OF_MEMORY; 383 } 384 385 const RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); 386 if (!data) { 387 return NS_ERROR_OUT_OF_MEMORY; 388 } 389 390 { 391 DataSourceSurface::MappedSurface map; 392 if (!data->Map(gfx::DataSourceSurface::MapType::READ, &map)) { 393 return NS_ERROR_INVALID_ARG; 394 } 395 auto size = data->GetSize(); 396 rv = aEncoder->InitFromData(map.mData, size.width * size.height * 4, 397 size.width, size.height, size.width * 4, 398 imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions, 399 VoidCString()); 400 data->Unmap(); 401 } 402 if (NS_SUCCEEDED(rv)) { 403 imgStream = aEncoder; 404 } 405 } else if (aImage) { 406 // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread. 407 // Other image formats could have problem to convert format off-main-thread. 408 // So here it uses a help function GetBRGADataSourceSurfaceSync() to convert 409 // format on main thread. 410 if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) { 411 nsTArray<uint8_t> data; 412 layers::PlanarYCbCrImage* ycbcrImage = 413 static_cast<layers::PlanarYCbCrImage*>(aImage); 414 gfxImageFormat format = SurfaceFormat::A8R8G8B8_UINT32; 415 int32_t stride = GetAlignedStride<16>(aSize.width, 4); 416 size_t length = BufferSizeFromStrideAndHeight(stride, aSize.height); 417 if (length == 0) { 418 return NS_ERROR_INVALID_ARG; 419 } 420 data.SetCapacity(length); 421 422 rv = ConvertYCbCrToRGB(*ycbcrImage->GetData(), format, 423 aSize.ToUnknownSize(), data.Elements(), stride); 424 if (NS_FAILED(rv)) { 425 MOZ_ASSERT_UNREACHABLE("Failed to convert YUV into RGB data"); 426 return rv; 427 } 428 429 rv = aEncoder->InitFromData( 430 data.Elements(), aSize.width * aSize.height * 4, aSize.width, 431 aSize.height, aSize.width * 4, imgIEncoder::INPUT_FORMAT_HOSTARGB, 432 aOptions, VoidCString()); 433 } else { 434 if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) { 435 return NS_ERROR_INVALID_ARG; 436 } 437 438 RefPtr<gfx::DataSourceSurface> dataSurface; 439 RefPtr<layers::Image> image(aImage); 440 dataSurface = GetBRGADataSourceSurfaceSync(image.forget()); 441 442 DataSourceSurface::MappedSurface map; 443 if (!dataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) { 444 return NS_ERROR_INVALID_ARG; 445 } 446 auto size = dataSurface->GetSize(); 447 rv = aEncoder->InitFromData(map.mData, size.width * size.height * 4, 448 size.width, size.height, size.width * 4, 449 imgIEncoder::INPUT_FORMAT_HOSTARGB, aOptions, 450 VoidCString()); 451 dataSurface->Unmap(); 452 } 453 454 if (NS_SUCCEEDED(rv)) { 455 imgStream = aEncoder; 456 } 457 } 458 NS_ENSURE_SUCCESS(rv, rv); 459 460 imgStream.forget(aStream); 461 return rv; 462 } 463 464 /* static */ 465 already_AddRefed<imgIEncoder> ImageEncoder::GetImageEncoder(nsAString& aType) { 466 // Get an image encoder for the media type. 467 nsCString encoderCID("@mozilla.org/image/encoder;2?type="); 468 NS_ConvertUTF16toUTF8 encoderType(aType); 469 encoderCID += encoderType; 470 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get()); 471 472 if (!encoder && aType != u"image/png"_ns) { 473 // Unable to create an encoder instance of the specified type. Falling back 474 // to PNG. 475 aType.AssignLiteral("image/png"); 476 nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png"); 477 encoder = do_CreateInstance(PNGEncoderCID.get()); 478 } 479 480 return encoder.forget(); 481 } 482 483 } // namespace mozilla::dom