// 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/geometry/size.h"
#include "ui/gfx/image/image_png_rep.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.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;
  }

  DCHECK(false);
  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;
  }

  DCHECK(false);
  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));
}