text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "components/viz/common/frame_sinks/copy_output_result.h"
  
  #include <utility>
  
  #include "base/check_op.h"
  #include "base/notreached.h"
  #include "third_party/libyuv/include/libyuv.h"
  #include "third_party/skia/include/core/SkPixelRef.h"
  #include "ui/gfx/color_space.h"
  
  namespace viz {
  
  CopyOutputResult::CopyOutputResult(Format format,
                                     const gfx::Rect& rect,
                                     bool needs_lock_for_bitmap)
      : format_(format),
        rect_(rect),
        needs_lock_for_bitmap_(needs_lock_for_bitmap) {
    DCHECK(format_ == Format::RGBA_BITMAP || format_ == Format::RGBA_TEXTURE ||
           format_ == Format::I420_PLANES);
  }
  
  CopyOutputResult::~CopyOutputResult() = default;
  
  bool CopyOutputResult::IsEmpty() const {
    if (rect_.IsEmpty())
      return true;
    switch (format_) {
      case Format::RGBA_BITMAP:
      case Format::I420_PLANES:
        return false;
      case Format::RGBA_TEXTURE:
        if (const TextureResult* result = GetTextureResult())
          return result->mailbox.IsZero();
        else
          return true;
    }
    NOTREACHED();
    return true;
  }
  
  bool CopyOutputResult::LockSkBitmap() const {
    return true;
  }
  
  void CopyOutputResult::UnlockSkBitmap() const {}
  
  const SkBitmap& CopyOutputResult::AsSkBitmap() const {
    DCHECK(!cached_bitmap_.readyToDraw() || cached_bitmap_.colorSpace());
    return cached_bitmap_;
  }
  
  CopyOutputResult::ScopedSkBitmap CopyOutputResult::ScopedAccessSkBitmap()
      const {
    return ScopedSkBitmap(this);
  }
  
  const CopyOutputResult::TextureResult* CopyOutputResult::GetTextureResult()
      const {
    return nullptr;
  }
  
  std::unique_ptr<SingleReleaseCallback>
  CopyOutputResult::TakeTextureOwnership() {
    return nullptr;
  }
  
  bool CopyOutputResult::ReadI420Planes(uint8_t* y_out,
                                        int y_out_stride,
                                        uint8_t* u_out,
                                        int u_out_stride,
                                        uint8_t* v_out,
                                        int v_out_stride) const {
    auto scoped_sk_bitmap = ScopedAccessSkBitmap();
    const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
    if (!bitmap.readyToDraw())
      return false;
    const uint8_t* pixels = static_cast<uint8_t*>(bitmap.getPixels());
    // The conversion below ignores color space completely, and it's not even
    // sRGB→Rec.709. Unfortunately, hand-optimized routines are not available, and
    // a perfect conversion using gfx::ColorTransform would execute way too
    // slowly. See SoftwareRenderer for related comments on its lack of color
    // space management (due to performance concerns).
    if (bitmap.colorType() == kBGRA_8888_SkColorType) {
      return 0 == libyuv::ARGBToI420(pixels, bitmap.rowBytes(), y_out,
                                     y_out_stride, u_out, u_out_stride, v_out,
                                     v_out_stride, bitmap.width(),
                                     bitmap.height());
    } else if (bitmap.colorType() == kRGBA_8888_SkColorType) {
      return 0 == libyuv::ABGRToI420(pixels, bitmap.rowBytes(), y_out,
                                     y_out_stride, u_out, u_out_stride, v_out,
                                     v_out_stride, bitmap.width(),
                                     bitmap.height());
    }
  
    // Other SkBitmap color types could be supported, but are currently never
    // being used.
    NOTIMPLEMENTED();
    return false;
  }
  
  bool CopyOutputResult::ReadRGBAPlane(uint8_t* dest, int stride) const {
    auto scoped_sk_bitmap = ScopedAccessSkBitmap();
    const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
    if (!bitmap.readyToDraw())
      return false;
    DCHECK(bitmap.colorSpace());
    SkImageInfo image_info =
        SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), kPremul_SkAlphaType,
                             bitmap.refColorSpace());
    bitmap.readPixels(image_info, dest, stride, 0, 0);
    return true;
  }
  
  gfx::ColorSpace CopyOutputResult::GetRGBAColorSpace() const {
    auto scoped_sk_bitmap = ScopedAccessSkBitmap();
    const SkBitmap& bitmap = scoped_sk_bitmap.bitmap();
    if (!bitmap.readyToDraw())
      return gfx::ColorSpace();
    DCHECK(bitmap.colorSpace());
    return gfx::ColorSpace(*(bitmap.colorSpace()));
  }
  
  CopyOutputSkBitmapResult::CopyOutputSkBitmapResult(const gfx::Rect& rect,
                                                     SkBitmap bitmap)
      : CopyOutputSkBitmapResult(Format::RGBA_BITMAP, rect, std::move(bitmap)) {}
  
  CopyOutputSkBitmapResult::CopyOutputSkBitmapResult(
      CopyOutputResult::Format format,
      const gfx::Rect& rect,
      SkBitmap bitmap)
      : CopyOutputResult(format, rect, false) {
    DCHECK(format == Format::RGBA_BITMAP || format == Format::I420_PLANES);
    if (!rect.IsEmpty()) {
      DCHECK(!bitmap.pixelRef() || bitmap.pixelRef()->unique());
      DCHECK(!bitmap.readyToDraw() || bitmap.colorSpace());
      // Hold a reference to the |bitmap|'s pixels, for AsSkBitmap().
      *(cached_bitmap()) = std::move(bitmap);
    }
  }
  
  const SkBitmap& CopyOutputSkBitmapResult::AsSkBitmap() const {
    SkBitmap* const bitmap = cached_bitmap();
  
    if (rect().IsEmpty())
      return *bitmap;  // Return "null" bitmap for empty result.
  
    const SkImageInfo image_info = SkImageInfo::MakeN32Premul(
        rect().width(), rect().height(), bitmap->refColorSpace());
    if (bitmap->info() == image_info && bitmap->readyToDraw())
      return *bitmap;  // Return bitmap in expected format.
  
    // The bitmap is not in the "native optimized" format. Convert it once for
    // this and all future calls of this method.
    SkBitmap replacement;
    replacement.allocPixels(image_info);
    replacement.eraseColor(SK_ColorBLACK);
    SkPixmap src_pixmap;
    if (bitmap->peekPixels(&src_pixmap)) {
      // Note: writePixels() can fail, but then the replacement bitmap will be
      // left with part/all solid black due to the eraseColor() call above.
      replacement.writePixels(src_pixmap);
    }
    *bitmap = replacement;
  
    return *bitmap;
  }
  
  CopyOutputSkBitmapResult::~CopyOutputSkBitmapResult() = default;
  
  CopyOutputTextureResult::CopyOutputTextureResult(
      const gfx::Rect& rect,
      const gpu::Mailbox& mailbox,
      const gpu::SyncToken& sync_token,
      const gfx::ColorSpace& color_space,
      std::unique_ptr<SingleReleaseCallback> release_callback)
      : CopyOutputResult(Format::RGBA_TEXTURE, rect, false),
        texture_result_(mailbox, sync_token, color_space),
        release_callback_(std::move(release_callback)) {
    DCHECK_EQ(rect.IsEmpty(), mailbox.IsZero());
    DCHECK_EQ(!release_callback_, mailbox.IsZero());
    DCHECK(texture_result_.mailbox.IsZero() ||
           texture_result_.color_space.IsValid());
  }
  
  CopyOutputTextureResult::~CopyOutputTextureResult() {
    if (release_callback_)
      release_callback_->Run(gpu::SyncToken(), false);
  }
  
  const CopyOutputResult::TextureResult*
  CopyOutputTextureResult::GetTextureResult() const {
    return &texture_result_;
  }
  
  std::unique_ptr<SingleReleaseCallback>
  CopyOutputTextureResult::TakeTextureOwnership() {
    texture_result_.mailbox = gpu::Mailbox();
    texture_result_.sync_token = gpu::SyncToken();
    texture_result_.color_space = gfx::ColorSpace();
    return std::move(release_callback_);
  }
  
  CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap() = default;
  
  CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap(
      const CopyOutputResult* result) {
    DCHECK(result);
    if (!result->needs_lock_for_bitmap_ || result->LockSkBitmap())
      result_ = result;
  }
  
  CopyOutputResult::ScopedSkBitmap::ScopedSkBitmap(ScopedSkBitmap&& other) {
    *this = std::move(other);
  }
  
  CopyOutputResult::ScopedSkBitmap::~ScopedSkBitmap() {
    reset();
  }
  
  CopyOutputResult::ScopedSkBitmap& CopyOutputResult::ScopedSkBitmap::operator=(
      ScopedSkBitmap&& other) {
    DCHECK_CALLED_ON_VALID_THREAD(other.thread_checker_);
    reset();
    std::swap(result_, other.result_);
    return *this;
  }
  
  void CopyOutputResult::ScopedSkBitmap::reset() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (!result_)
      return;
    if (result_->needs_lock_for_bitmap_) {
  #if DCHECK_IS_ON()
      // We are going to unlock the content of the bitmap, so we need to make
      // sure there is no other ref of the bitmap content.
      auto* ref = bitmap().pixelRef();
      DCHECK(!ref || ref->unique());
  #endif
      result_->UnlockSkBitmap();
      result_ = nullptr;
    }
  }
  
  SkBitmap CopyOutputResult::ScopedSkBitmap::GetOutScopedBitmap() const {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (!result_)
      return SkBitmap();
  
    const auto& bitmap = result_->AsSkBitmap();
  
    // If result_->needs_lock_for_bitmap_ is false, then bitmap can be used out of
    // the scope.
    if (!result_->needs_lock_for_bitmap_)
      return bitmap;
  
    // Make a new SkBitmap and copy content to this new SkBitmap.
    SkBitmap bitmap_copy;
    if (bitmap.readyToDraw()) {
      bitmap_copy.allocPixels(bitmap.info());
      bitmap.readPixels(bitmap_copy.pixmap(), 0, 0);
    }
    return bitmap_copy;
  }
  
  }  // namespace viz
  