text: // Copyright (c) 2016 The Chromium Embedded Framework 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 "libcef/browser/image_impl.h"
  
  #include <algorithm>
  
  #include "skia/ext/skia_utils_base.h"
  #include "ui/gfx/codec/jpeg_codec.h"
  #include "ui/gfx/codec/png_codec.h"
  #include "ui/gfx/image/image_png_rep.h"
  #include "ui/gfx/image/image_skia.h"
  
  namespace {
  
  SkColorType GetSkColorType(cef_color_type_t color_type) {
    switch (color_type) {
      case CEF_COLOR_TYPE_RGBA_8888:
        return kRGBA_8888_SkColorType;
      case CEF_COLOR_TYPE_BGRA_8888:
        return kBGRA_8888_SkColorType;
      default:
        break;
    }
  
    NOTREACHED();
    return kUnknown_SkColorType;
  }
  
  SkAlphaType GetSkAlphaType(cef_alpha_type_t alpha_type) {
    switch (alpha_type) {
      case CEF_ALPHA_TYPE_OPAQUE:
        return kOpaque_SkAlphaType;
      case CEF_ALPHA_TYPE_PREMULTIPLIED:
        return kPremul_SkAlphaType;
      case CEF_ALPHA_TYPE_POSTMULTIPLIED:
        return kUnpremul_SkAlphaType;
      default:
        break;
    }
  
    NOTREACHED();
    return kUnknown_SkAlphaType;
  }
  
  // Compress as PNG. Requires post-multiplied alpha.
  bool PNGMethod(bool with_transparency,
                 const SkBitmap& bitmap,
                 std::vector<unsigned char>* compressed) {
    return gfx::PNGCodec::Encode(
        reinterpret_cast<unsigned char*>(bitmap.getPixels()),
        bitmap.colorType() == kBGRA_8888_SkColorType ? gfx::PNGCodec::FORMAT_BGRA
                                                     : gfx::PNGCodec::FORMAT_RGBA,
        gfx::Size(bitmap.width(), bitmap.height()),
        static_cast<int>(bitmap.rowBytes()),
        bitmap.alphaType() == kOpaque_SkAlphaType || !with_transparency,
        std::vector<gfx::PNGCodec::Comment>(), compressed);
  }
  
  // Compress as JPEG. This internally uses JCS_EXT_RGBX or JCS_EXT_BGRX which
  // causes the alpha channel to be ignored. Requires post-multiplied alpha.
  bool JPEGMethod(int quality,
                  const SkBitmap& bitmap,
                  std::vector<unsigned char>* compressed) {
    return gfx::JPEGCodec::Encode(bitmap, quality, compressed);
  }
  
  }  // namespace
  
  // static
  CefRefPtr<CefImage> CefImage::CreateImage() {
    return new CefImageImpl();
  }
  
  CefImageImpl::CefImageImpl(const gfx::ImageSkia& image_skia)
      : image_(image_skia) {}
  
  bool CefImageImpl::IsEmpty() {
    base::AutoLock lock_scope(lock_);
    return image_.IsEmpty();
  }
  
  bool CefImageImpl::IsSame(CefRefPtr<CefImage> that) {
    CefImageImpl* that_impl = static_cast<CefImageImpl*>(that.get());
    if (!that_impl)
      return false;
  
    // Quick check for the same object.
    if (this == that_impl)
      return true;
  
    base::AutoLock lock_scope(lock_);
    return image_.AsImageSkia().BackedBySameObjectAs(
        that_impl->image_.AsImageSkia());
  }
  
  bool CefImageImpl::AddBitmap(float scale_factor,
                               int pixel_width,
                               int pixel_height,
                               cef_color_type_t color_type,
                               cef_alpha_type_t alpha_type,
                               const void* pixel_data,
                               size_t pixel_data_size) {
    const SkColorType ct = GetSkColorType(color_type);
    const SkAlphaType at = GetSkAlphaType(alpha_type);
  
    // Make sure the client passed in the expected values.
    if (ct != kBGRA_8888_SkColorType && ct != kRGBA_8888_SkColorType)
      return false;
    if (pixel_data_size != pixel_width * pixel_height * 4U)
      return false;
  
    SkBitmap bitmap;
    if (!bitmap.tryAllocPixels(
            SkImageInfo::Make(pixel_width, pixel_height, ct, at))) {
      return false;
    }
  
    DCHECK_EQ(pixel_data_size, bitmap.computeByteSize());
    memcpy(bitmap.getPixels(), pixel_data, pixel_data_size);
  
    return AddBitmap(scale_factor, bitmap);
  }
  
  bool CefImageImpl::AddPNG(float scale_factor,
                            const void* png_data,
                            size_t png_data_size) {
    SkBitmap bitmap;
    if (!gfx::PNGCodec::Decode(static_cast<const unsigned char*>(png_data),
                               png_data_size, &bitmap)) {
      return false;
    }
  
    return AddBitmap(scale_factor, bitmap);
  }
  
  bool CefImageImpl::AddJPEG(float scale_factor,
                             const void* jpeg_data,
                             size_t jpeg_data_size) {
    std::unique_ptr<SkBitmap> bitmap(gfx::JPEGCodec::Decode(
        static_cast<const unsigned char*>(jpeg_data), jpeg_data_size));
    if (!bitmap.get())
      return false;
  
    return AddBitmap(scale_factor, *bitmap);
  }
  
  size_t CefImageImpl::GetWidth() {
    base::AutoLock lock_scope(lock_);
    return image_.Width();
  }
  
  size_t CefImageImpl::GetHeight() {
    base::AutoLock lock_scope(lock_);
    return image_.Height();
  }
  
  bool CefImageImpl::HasRepresentation(float scale_factor) {
    base::AutoLock lock_scope(lock_);
    return image_.AsImageSkia().HasRepresentation(scale_factor);
  }
  
  bool CefImageImpl::RemoveRepresentation(float scale_factor) {
    base::AutoLock lock_scope(lock_);
    gfx::ImageSkia image_skia = image_.AsImageSkia();
    if (image_skia.HasRepresentation(scale_factor)) {
      image_skia.RemoveRepresentation(scale_factor);
      return true;
    }
    return false;
  }
  
  bool CefImageImpl::GetRepresentationInfo(float scale_factor,
                                           float& actual_scale_factor,
                                           int& pixel_width,
                                           int& pixel_height) {
    base::AutoLock lock_scope(lock_);
    gfx::ImageSkia image_skia = image_.AsImageSkia();
    if (image_skia.isNull())
      return false;
  
    const gfx::ImageSkiaRep& rep = image_skia.GetRepresentation(scale_factor);
    if (rep.is_null())
      return false;
  
    actual_scale_factor = rep.scale();
    pixel_width = rep.GetBitmap().width();
    pixel_height = rep.GetBitmap().height();
    return true;
  }
  
  CefRefPtr<CefBinaryValue> CefImageImpl::GetAsBitmap(float scale_factor,
                                                      cef_color_type_t color_type,
                                                      cef_alpha_type_t alpha_type,
                                                      int& pixel_width,
                                                      int& pixel_height) {
    const SkColorType desired_ct = GetSkColorType(color_type);
    const SkAlphaType desired_at = GetSkAlphaType(alpha_type);
  
    base::AutoLock lock_scope(lock_);
    const SkBitmap* bitmap = GetBitmap(scale_factor);
    if (!bitmap)
      return nullptr;
  
    DCHECK(bitmap->readyToDraw());
  
    pixel_width = bitmap->width();
    pixel_height = bitmap->height();
  
    if (bitmap->colorType() == desired_ct && bitmap->alphaType() == desired_at) {
      // No conversion necessary.
      return CefBinaryValue::Create(bitmap->getPixels(),
                                    bitmap->computeByteSize());
    } else {
      SkBitmap desired_bitmap;
      if (!ConvertBitmap(*bitmap, &desired_bitmap, desired_ct, desired_at))
        return nullptr;
      DCHECK(desired_bitmap.readyToDraw());
      return CefBinaryValue::Create(desired_bitmap.getPixels(),
                                    desired_bitmap.computeByteSize());
    }
  }
  
  CefRefPtr<CefBinaryValue> CefImageImpl::GetAsPNG(float scale_factor,
                                                   bool with_transparency,
                                                   int& pixel_width,
                                                   int& pixel_height) {
    base::AutoLock lock_scope(lock_);
    const SkBitmap* bitmap = GetBitmap(scale_factor);
    if (!bitmap)
      return nullptr;
  
    std::vector<unsigned char> compressed;
    if (!WritePNG(*bitmap, &compressed, with_transparency))
      return nullptr;
  
    pixel_width = bitmap->width();
    pixel_height = bitmap->height();
  
    return CefBinaryValue::Create(&compressed.front(), compressed.size());
  }
  
  CefRefPtr<CefBinaryValue> CefImageImpl::GetAsJPEG(float scale_factor,
                                                    int quality,
                                                    int& pixel_width,
                                                    int& pixel_height) {
    base::AutoLock lock_scope(lock_);
    const SkBitmap* bitmap = GetBitmap(scale_factor);
    if (!bitmap)
      return nullptr;
  
    std::vector<unsigned char> compressed;
    if (!WriteJPEG(*bitmap, &compressed, quality))
      return nullptr;
  
    pixel_width = bitmap->width();
    pixel_height = bitmap->height();
  
    return CefBinaryValue::Create(&compressed.front(), compressed.size());
  }
  
  void CefImageImpl::AddBitmaps(int32_t scale_1x_size,
                                const std::vector<SkBitmap>& bitmaps) {
    if (scale_1x_size == 0) {
      // Set the scale 1x size to the smallest bitmap pixel size.
      int32_t min_size = std::numeric_limits<int32_t>::max();
      for (const SkBitmap& bitmap : bitmaps) {
        const int32_t size = std::max(bitmap.width(), bitmap.height());
        if (size < min_size)
          min_size = size;
      }
      scale_1x_size = min_size;
    }
  
    for (const SkBitmap& bitmap : bitmaps) {
      const int32_t size = std::max(bitmap.width(), bitmap.height());
      const float scale_factor =
          static_cast<float>(size) / static_cast<float>(scale_1x_size);
      AddBitmap(scale_factor, bitmap);
    }
  }
  
  gfx::ImageSkia CefImageImpl::GetForced1xScaleRepresentation(
      float scale_factor) const {
    base::AutoLock lock_scope(lock_);
    if (scale_factor == 1.0f) {
      // We can use the existing image without modification.
      return image_.AsImageSkia();
    }
  
    const SkBitmap* bitmap = GetBitmap(scale_factor);
    gfx::ImageSkia image_skia;
    if (bitmap)
      image_skia.AddRepresentation(gfx::ImageSkiaRep(*bitmap, 1.0f));
    return image_skia;
  }
  
  gfx::ImageSkia CefImageImpl::AsImageSkia() const {
    base::AutoLock lock_scope(lock_);
    return image_.AsImageSkia();
  }
  
  bool CefImageImpl::AddBitmap(float scale_factor, const SkBitmap& bitmap) {
  #if DCHECK_IS_ON()
    DCHECK(bitmap.readyToDraw());
  #endif
    DCHECK(bitmap.colorType() == kBGRA_8888_SkColorType ||
           bitmap.colorType() == kRGBA_8888_SkColorType);
  
    // Convert to N32 (e.g. native encoding) format if not already in that format.
    // N32 is expected by the Views framework and this early conversion avoids
    // CHECKs in ImageSkiaRep and eventual conversion to N32 at some later point
    // in the compositing pipeline.
    SkBitmap n32_bitmap;
    if (!skia::SkBitmapToN32OpaqueOrPremul(bitmap, &n32_bitmap))
      return false;
  
    gfx::ImageSkiaRep skia_rep(n32_bitmap, scale_factor);
    base::AutoLock lock_scope(lock_);
    if (image_.IsEmpty()) {
      image_ = gfx::Image(gfx::ImageSkia(skia_rep));
    } else {
      image_.AsImageSkia().AddRepresentation(skia_rep);
    }
    return true;
  }
  
  const SkBitmap* CefImageImpl::GetBitmap(float scale_factor) const {
    lock_.AssertAcquired();
    gfx::ImageSkia image_skia = image_.AsImageSkia();
    if (image_skia.isNull())
      return nullptr;
  
    const gfx::ImageSkiaRep& rep = image_skia.GetRepresentation(scale_factor);
    if (rep.is_null())
      return nullptr;
  
    return &rep.GetBitmap();
  }
  
  // static
  bool CefImageImpl::ConvertBitmap(const SkBitmap& src_bitmap,
                                   SkBitmap* target_bitmap,
                                   SkColorType target_ct,
                                   SkAlphaType target_at) {
    DCHECK(src_bitmap.readyToDraw());
    DCHECK(src_bitmap.colorType() != target_ct ||
           src_bitmap.alphaType() != target_at);
    DCHECK(target_bitmap);
  
    SkImageInfo target_info = SkImageInfo::Make(
        src_bitmap.width(), src_bitmap.height(), target_ct, target_at);
    if (!target_bitmap->tryAllocPixels(target_info))
      return false;
  
    if (!src_bitmap.readPixels(target_info, target_bitmap->getPixels(),
                               target_bitmap->rowBytes(), 0, 0)) {
      return false;
    }
  
    DCHECK(target_bitmap->readyToDraw());
    return true;
  }
  
  // static
  bool CefImageImpl::WriteCompressedFormat(const SkBitmap& bitmap,
                                           std::vector<unsigned char>* compressed,
                                           CompressionMethod method) {
    const SkBitmap* bitmap_ptr = nullptr;
    SkBitmap bitmap_postalpha;
    if (bitmap.alphaType() == kPremul_SkAlphaType) {
      // Compression methods require post-multiplied alpha values.
      if (!ConvertBitmap(bitmap, &bitmap_postalpha, bitmap.colorType(),
                         kUnpremul_SkAlphaType)) {
        return false;
      }
      bitmap_ptr = &bitmap_postalpha;
    } else {
      bitmap_ptr = &bitmap;
    }
  
    DCHECK(bitmap_ptr->readyToDraw());
    DCHECK(bitmap_ptr->colorType() == kBGRA_8888_SkColorType ||
           bitmap_ptr->colorType() == kRGBA_8888_SkColorType);
    DCHECK(bitmap_ptr->alphaType() == kOpaque_SkAlphaType ||
           bitmap_ptr->alphaType() == kUnpremul_SkAlphaType);
  
    return std::move(method).Run(*bitmap_ptr, compressed);
  }
  
  // static
  bool CefImageImpl::WritePNG(const SkBitmap& bitmap,
                              std::vector<unsigned char>* compressed,
                              bool with_transparency) {
    return WriteCompressedFormat(bitmap, compressed,
                                 base::BindOnce(PNGMethod, with_transparency));
  }
  
  // static
  bool CefImageImpl::WriteJPEG(const SkBitmap& bitmap,
                               std::vector<unsigned char>* compressed,
                               int quality) {
    return WriteCompressedFormat(bitmap, compressed,
                                 base::BindOnce(JPEGMethod, quality));
  }
  