// Copyright 2013 The Flutter 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 "flutter/lib/ui/painting/image_decoder_impeller.h"

#include <format>
#include <memory>

#include "flutter/fml/closure.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/trace_event.h"
#include "flutter/impeller/core/allocator.h"
#include "flutter/impeller/display_list/dl_image_impeller.h"
#include "flutter/impeller/renderer/command_buffer.h"
#include "flutter/impeller/renderer/context.h"
#include "impeller/core/device_buffer.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture_descriptor.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/geometry/size.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkSize.h"

namespace flutter {

namespace {
/**
 *  Loads the gamut as a set of three points (triangle).
 */
void LoadGamut(SkPoint abc[3], const skcms_Matrix3x3& xyz) {
  // rx = rX / (rX + rY + rZ)
  // ry = rY / (rX + rY + rZ)
  // gx, gy, bx, and gy are calculated similarly.
  for (int index = 0; index < 3; index++) {
    float sum = xyz.vals[index][0] + xyz.vals[index][1] + xyz.vals[index][2];
    abc[index].fX = xyz.vals[index][0] / sum;
    abc[index].fY = xyz.vals[index][1] / sum;
  }
}

/**
 *  Calculates the area of the triangular gamut.
 */
float CalculateArea(SkPoint abc[3]) {
  const SkPoint& a = abc[0];
  const SkPoint& b = abc[1];
  const SkPoint& c = abc[2];
  return 0.5f * fabsf(a.fX * b.fY + b.fX * c.fY - a.fX * c.fY - c.fX * b.fY -
                      b.fX * a.fY);
}

// Note: This was calculated from SkColorSpace::MakeSRGB().
static constexpr float kSrgbGamutArea = 0.0982f;

// Source:
// https://source.chromium.org/chromium/_/skia/skia.git/+/393fb1ec80f41d8ad7d104921b6920e69749fda1:src/codec/SkAndroidCodec.cpp;l=67;drc=46572b4d445f41943059d0e377afc6d6748cd5ca;bpv=1;bpt=0
bool IsWideGamut(const SkColorSpace* color_space) {
  if (!color_space) {
    return false;
  }
  skcms_Matrix3x3 xyzd50;
  color_space->toXYZD50(&xyzd50);
  SkPoint rgb[3];
  LoadGamut(rgb, xyzd50);
  float area = CalculateArea(rgb);
  return area > kSrgbGamutArea;
}

absl::StatusOr<ImageDecoderImpeller::ImageInfo> ToImageInfo(
    const SkImageInfo& sk_image_format) {
  const std::optional<impeller::PixelFormat> pixel_format =
      ImageDecoderImpeller::ToPixelFormat(sk_image_format.colorType());
  if (!pixel_format.has_value()) {
    std::string decode_error(
        std::format("Codec pixel format is not supported (SkColorType={})",
                    static_cast<int>(sk_image_format.colorType())));
    return absl::InvalidArgumentError(decode_error);
  }
  return {{
      .size =
          impeller::ISize(sk_image_format.width(), sk_image_format.height()),
      .format = pixel_format.value(),
  }};
}

SkColorType ChooseCompatibleColorType(SkColorType type) {
  switch (type) {
    case kRGBA_F32_SkColorType:
      return kRGBA_F16_SkColorType;
    default:
      return kRGBA_8888_SkColorType;
  }
}

SkAlphaType ChooseCompatibleAlphaType(SkAlphaType type) {
  return type;
}

absl::StatusOr<SkColorType> ChooseCompatibleColorType(
    ImageDecoder::TargetPixelFormat format) {
  switch (format) {
    case ImageDecoder::TargetPixelFormat::kR32G32B32A32Float:
      return kRGBA_F32_SkColorType;
    default:
      return absl::InvalidArgumentError("unsupported target pixel format");
  }
}

absl::StatusOr<SkImageInfo> CreateImageInfo(
    const SkImageInfo& base_image_info,
    const SkISize& decode_size,
    bool supports_wide_gamut,
    ImageDecoder::TargetPixelFormat target_format) {
  const bool is_wide_gamut =
      supports_wide_gamut ? IsWideGamut(base_image_info.colorSpace()) : false;
  SkAlphaType alpha_type =
      ChooseCompatibleAlphaType(base_image_info.alphaType());
  if (target_format != ImageDecoder::TargetPixelFormat::kDontCare) {
    absl::StatusOr<SkColorType> target_skia_color_type =
        ChooseCompatibleColorType(target_format);
    if (!target_skia_color_type.ok()) {
      return target_skia_color_type.status();
    }
    return base_image_info.makeWH(decode_size.width(), decode_size.height())
        .makeColorType(target_skia_color_type.value())
        .makeAlphaType(alpha_type)
        .makeColorSpace(SkColorSpace::MakeSRGB());
  } else if (is_wide_gamut) {
    SkColorType color_type = alpha_type == SkAlphaType::kOpaque_SkAlphaType
                                 ? kBGR_101010x_XR_SkColorType
                                 : kRGBA_F16_SkColorType;
    return base_image_info.makeWH(decode_size.width(), decode_size.height())
        .makeColorType(color_type)
        .makeAlphaType(alpha_type)
        .makeColorSpace(SkColorSpace::MakeSRGB());
  } else {
    return base_image_info.makeWH(decode_size.width(), decode_size.height())
        .makeColorType(ChooseCompatibleColorType(base_image_info.colorType()))
        .makeAlphaType(alpha_type)
        .makeColorSpace(SkColorSpace::MakeSRGB());
  }
}

struct DecodedBitmap {
  std::shared_ptr<SkBitmap> bitmap;
  std::shared_ptr<ImpellerAllocator> allocator;
};

absl::StatusOr<DecodedBitmap> DecodeToBitmap(
    ImageDescriptor* descriptor,
    const SkImageInfo& image_info,
    const SkImageInfo& base_image_info,
    const std::shared_ptr<impeller::Allocator>& allocator) {
  std::shared_ptr<SkBitmap> bitmap = std::make_shared<SkBitmap>();
  bitmap->setInfo(image_info);
  std::shared_ptr<ImpellerAllocator> bitmap_allocator =
      std::make_shared<ImpellerAllocator>(allocator);

  if (descriptor->is_compressed()) {
    if (!bitmap->tryAllocPixels(bitmap_allocator.get())) {
      std::string error =
          "Could not allocate intermediate for image decompression.";
      FML_DLOG(ERROR) << error;
      return absl::InvalidArgumentError(error);
    }
    if (!descriptor->get_pixels(bitmap->pixmap())) {
      std::string error = "Could not decompress image.";
      FML_DLOG(ERROR) << error;
      return absl::InvalidArgumentError(error);
    }
  } else {
    std::shared_ptr<SkBitmap> temp_bitmap = std::make_shared<SkBitmap>();
    temp_bitmap->setInfo(base_image_info);
    sk_sp<SkPixelRef> pixel_ref = SkMallocPixelRef::MakeWithData(
        base_image_info, descriptor->row_bytes(), descriptor->data());
    temp_bitmap->setPixelRef(pixel_ref, 0, 0);

    if (!bitmap->tryAllocPixels(bitmap_allocator.get())) {
      std::string error =
          "Could not allocate intermediate for pixel conversion.";
      FML_DLOG(ERROR) << error;
      return absl::ResourceExhaustedError(error);
    }
    temp_bitmap->readPixels(bitmap->pixmap());
    bitmap->setImmutable();
  }
  return DecodedBitmap{.bitmap = bitmap, .allocator = bitmap_allocator};
}

absl::StatusOr<DecodedBitmap> HandlePremultiplication(
    const std::shared_ptr<SkBitmap>& bitmap,
    std::shared_ptr<ImpellerAllocator> bitmap_allocator,
    const std::shared_ptr<impeller::Allocator>& allocator) {
  if (bitmap->alphaType() != SkAlphaType::kUnpremul_SkAlphaType) {
    return DecodedBitmap{.bitmap = bitmap,
                         .allocator = std::move(bitmap_allocator)};
  }

  std::shared_ptr<ImpellerAllocator> premul_allocator =
      std::make_shared<ImpellerAllocator>(allocator);
  std::shared_ptr<SkBitmap> premul_bitmap = std::make_shared<SkBitmap>();
  premul_bitmap->setInfo(bitmap->info().makeAlphaType(kPremul_SkAlphaType));
  if (!premul_bitmap->tryAllocPixels(premul_allocator.get())) {
    std::string error =
        "Could not allocate intermediate for premultiplication conversion.";
    FML_DLOG(ERROR) << error;
    return absl::ResourceExhaustedError(error);
  }
  bitmap->readPixels(premul_bitmap->pixmap());
  premul_bitmap->setImmutable();
  return DecodedBitmap{.bitmap = premul_bitmap, .allocator = premul_allocator};
}

absl::StatusOr<ImageDecoderImpeller::DecompressResult> ResizeOnCpu(
    const std::shared_ptr<SkBitmap>& bitmap,
    const SkISize& target_size,
    const std::shared_ptr<impeller::Allocator>& allocator) {
  TRACE_EVENT0("impeller", "SlowCPUDecodeScale");
  const SkImageInfo scaled_image_info =
      bitmap->info().makeDimensions(target_size);

  std::shared_ptr<SkBitmap> scaled_bitmap = std::make_shared<SkBitmap>();
  std::shared_ptr<ImpellerAllocator> scaled_allocator =
      std::make_shared<ImpellerAllocator>(allocator);
  scaled_bitmap->setInfo(scaled_image_info);
  if (!scaled_bitmap->tryAllocPixels(scaled_allocator.get())) {
    std::string error =
        "Could not allocate scaled bitmap for image decompression.";
    FML_DLOG(ERROR) << error;
    return absl::ResourceExhaustedError(error);
  }
  if (!bitmap->pixmap().scalePixels(
          scaled_bitmap->pixmap(),
          SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone))) {
    FML_LOG(ERROR) << "Could not scale decoded bitmap data.";
  }
  scaled_bitmap->setImmutable();

  std::shared_ptr<impeller::DeviceBuffer> buffer =
      scaled_allocator->GetDeviceBuffer();
  if (!buffer) {
    return absl::InternalError("Unable to get device buffer");
  }
  buffer->Flush();

  absl::StatusOr<ImageDecoderImpeller::ImageInfo> decoded_image_info =
      ToImageInfo(scaled_bitmap->info());
  if (!decoded_image_info.ok()) {
    return decoded_image_info.status();
  }
  return ImageDecoderImpeller::DecompressResult{
      .device_buffer = std::move(buffer),
      .image_info = decoded_image_info.value()};
}

bool IsZeroOpConversion(ImageDescriptor::PixelFormat input,
                        ImageDecoder::TargetPixelFormat output) {
  switch (input) {
    case ImageDescriptor::PixelFormat::kR32Float:
      return output == ImageDecoder::TargetPixelFormat::kR32Float;
    default:
      return false;
  }
}

impeller::PixelFormat ToImpellerPixelFormat(
    ImageDecoder::TargetPixelFormat format) {
  switch (format) {
    case ImageDecoder::TargetPixelFormat::kR32Float:
      return impeller::PixelFormat::kR32Float;
    default:
      FML_DCHECK(false) << "Unsupported pixel format";
      return impeller::PixelFormat::kUnknown;
  }
}
}  // namespace

std::optional<impeller::PixelFormat> ImageDecoderImpeller::ToPixelFormat(
    SkColorType type) {
  switch (type) {
    case kRGBA_8888_SkColorType:
      return impeller::PixelFormat::kR8G8B8A8UNormInt;
    case kBGRA_8888_SkColorType:
      return impeller::PixelFormat::kB8G8R8A8UNormInt;
    case kRGBA_F16_SkColorType:
      return impeller::PixelFormat::kR16G16B16A16Float;
    case kBGR_101010x_XR_SkColorType:
      return impeller::PixelFormat::kB10G10R10XR;
    case kRGBA_F32_SkColorType:
      return impeller::PixelFormat::kR32G32B32A32Float;
    default:
      return std::nullopt;
  }
  return std::nullopt;
}

ImageDecoderImpeller::ImageDecoderImpeller(
    const TaskRunners& runners,
    std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
    const fml::WeakPtr<IOManager>& io_manager,
    bool wide_gamut_enabled,
    const std::shared_ptr<fml::SyncSwitch>& gpu_disabled_switch)
    : ImageDecoder(runners, std::move(concurrent_task_runner), io_manager),
      wide_gamut_enabled_(wide_gamut_enabled),
      gpu_disabled_switch_(gpu_disabled_switch) {
  std::promise<std::shared_ptr<impeller::Context>> context_promise;
  context_ = context_promise.get_future();
  runners_.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
      [promise = std::move(context_promise), io_manager]() mutable {
        if (io_manager) {
          promise.set_value(io_manager->GetImpellerContext());
        } else {
          promise.set_value(nullptr);
        }
      }));
}

ImageDecoderImpeller::~ImageDecoderImpeller() = default;

absl::StatusOr<ImageDecoderImpeller::DecompressResult>
ImageDecoderImpeller::DecompressTexture(
    ImageDescriptor* descriptor,
    const ImageDecoder::Options& options,
    impeller::ISize max_texture_size,
    bool supports_wide_gamut,
    const std::shared_ptr<const impeller::Capabilities>& capabilities,
    const std::shared_ptr<impeller::Allocator>& allocator) {
  TRACE_EVENT0("impeller", __FUNCTION__);
  if (!descriptor) {
    std::string decode_error("Invalid descriptor (should never happen)");
    FML_DLOG(ERROR) << decode_error;
    return absl::InvalidArgumentError(decode_error);
  }

  const SkISize source_size = SkISize::Make(descriptor->image_info().width,
                                            descriptor->image_info().height);
  const SkISize target_size =
      SkISize::Make(std::min(max_texture_size.width,
                             static_cast<int64_t>(options.target_width)),
                    std::min(max_texture_size.height,
                             static_cast<int64_t>(options.target_height)));

  // Fast path for when the input requires no decompressing or conversion.
  if (!descriptor->is_compressed() && source_size == target_size &&
      IsZeroOpConversion(descriptor->image_info().format,
                         options.target_format)) {
    impeller::DeviceBufferDescriptor desc;
    desc.storage_mode = impeller::StorageMode::kHostVisible;
    desc.size = descriptor->data()->size();
    std::shared_ptr<impeller::DeviceBuffer> buffer =
        allocator->CreateBuffer(desc);
    if (!buffer) {
      return absl::ResourceExhaustedError("Could not create buffer for image.");
    }
    sk_sp<SkData> data = descriptor->data();
    memcpy(buffer->OnGetContents(), data->bytes(), data->size());
    buffer->Flush();

    return ImageDecoderImpeller::DecompressResult{
        .device_buffer = std::move(buffer),
        .image_info =
            {
                .size =
                    impeller::ISize(source_size.width(), source_size.height()),
                .format = ToImpellerPixelFormat(options.target_format),
            },
    };
  }

  SkISize decode_size = source_size;
  if (descriptor->is_compressed()) {
    decode_size = descriptor->get_scaled_dimensions(std::max(
        static_cast<float>(target_size.width()) / source_size.width(),
        static_cast<float>(target_size.height()) / source_size.height()));
  }

  const SkImageInfo base_image_info =
      ImageDescriptor::ToSkImageInfo(descriptor->image_info());
  const absl::StatusOr<SkImageInfo> image_info = CreateImageInfo(
      base_image_info, decode_size, supports_wide_gamut, options.target_format);

  if (!image_info.ok()) {
    return image_info.status();
  }

  if (!ToPixelFormat(image_info.value().colorType()).has_value()) {
    std::string decode_error =
        std::format("Codec pixel format is not supported (SkColorType={})",
                    static_cast<int>(image_info.value().colorType()));
    FML_DLOG(ERROR) << decode_error;
    return absl::InvalidArgumentError(decode_error);
  }

  absl::StatusOr<DecodedBitmap> decoded = DecodeToBitmap(
      descriptor, image_info.value(), base_image_info, allocator);
  if (!decoded.ok()) {
    return decoded.status();
  }

  absl::StatusOr<DecodedBitmap> premultiplied =
      HandlePremultiplication(decoded->bitmap, decoded->allocator, allocator);
  if (!premultiplied.ok()) {
    return premultiplied.status();
  }
  std::shared_ptr<SkBitmap> bitmap = premultiplied->bitmap;
  std::shared_ptr<ImpellerAllocator> bitmap_allocator =
      premultiplied->allocator;

  if (source_size.width() > max_texture_size.width ||
      source_size.height() > max_texture_size.height ||
      !capabilities->SupportsTextureToTextureBlits()) {
    return ResizeOnCpu(bitmap, target_size, allocator);
  }

  std::shared_ptr<impeller::DeviceBuffer> buffer =
      bitmap_allocator->GetDeviceBuffer();
  if (!buffer) {
    return absl::InternalError("Unable to get device buffer");
  }
  buffer->Flush();

  std::optional<SkImageInfo> resize_info =
      bitmap->dimensions() == target_size
          ? std::nullopt
          : std::optional<SkImageInfo>(
                image_info.value().makeDimensions(target_size));

  absl::StatusOr<ImageDecoderImpeller::ImageInfo> decoded_image_info =
      ToImageInfo(bitmap->info());
  if (!decoded_image_info.ok()) {
    return decoded_image_info.status();
  }

  return ImageDecoderImpeller::DecompressResult{
      .device_buffer = std::move(buffer),
      .image_info = decoded_image_info.value(),
      .resize_info = resize_info};
}

// static
std::pair<sk_sp<DlImage>, std::string>
ImageDecoderImpeller::UnsafeUploadTextureToPrivate(
    const std::shared_ptr<impeller::Context>& context,
    const std::shared_ptr<impeller::DeviceBuffer>& buffer,
    const ImageDecoderImpeller::ImageInfo& image_info,
    const std::optional<SkImageInfo>& resize_info) {
  impeller::TextureDescriptor texture_descriptor;
  texture_descriptor.storage_mode = impeller::StorageMode::kDevicePrivate;
  texture_descriptor.format = image_info.format;
  texture_descriptor.size = {image_info.size.width, image_info.size.height};
  texture_descriptor.mip_count = texture_descriptor.size.MipCount();
  if (context->GetBackendType() == impeller::Context::BackendType::kMetal &&
      resize_info.has_value()) {
    // The MPS used to resize images on iOS does not require mip generation.
    // Remove mip count if we are resizing the image on the GPU.
    texture_descriptor.mip_count = 1;
  }

  auto dest_texture =
      context->GetResourceAllocator()->CreateTexture(texture_descriptor);
  if (!dest_texture) {
    std::string decode_error("Could not create Impeller texture.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }

  dest_texture->SetLabel(
      std::format("ui.Image({})", static_cast<const void*>(dest_texture.get()))
          .c_str());

  auto command_buffer = context->CreateCommandBuffer();
  if (!command_buffer) {
    std::string decode_error(
        "Could not create command buffer for mipmap generation.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }
  command_buffer->SetLabel("Mipmap Command Buffer");

  auto blit_pass = command_buffer->CreateBlitPass();
  if (!blit_pass) {
    std::string decode_error(
        "Could not create blit pass for mipmap generation.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }
  blit_pass->SetLabel("Mipmap Blit Pass");
  blit_pass->AddCopy(impeller::DeviceBuffer::AsBufferView(buffer),
                     dest_texture);
  if (texture_descriptor.mip_count > 1) {
    blit_pass->GenerateMipmap(dest_texture);
  }

  std::shared_ptr<impeller::Texture> result_texture = dest_texture;
  if (resize_info.has_value()) {
    impeller::TextureDescriptor resize_desc;
    resize_desc.storage_mode = impeller::StorageMode::kDevicePrivate;
    resize_desc.format = image_info.format;
    resize_desc.size = {resize_info->width(), resize_info->height()};
    resize_desc.mip_count = resize_desc.size.MipCount();
    resize_desc.usage = impeller::TextureUsage::kShaderRead;
    if (context->GetBackendType() == impeller::Context::BackendType::kMetal) {
      // Resizing requires a MPS on Metal platforms.
      resize_desc.usage |= impeller::TextureUsage::kShaderWrite;
      resize_desc.compression_type = impeller::CompressionType::kLossless;
    }
    auto resize_texture =
        context->GetResourceAllocator()->CreateTexture(resize_desc);
    if (!resize_texture) {
      std::string decode_error("Could not create resized Impeller texture.");
      FML_DLOG(ERROR) << decode_error;
      return std::make_pair(nullptr, decode_error);
    }

    blit_pass->ResizeTexture(/*source=*/dest_texture,
                             /*destination=*/resize_texture);
    if (resize_desc.mip_count > 1) {
      blit_pass->GenerateMipmap(resize_texture);
    }

    result_texture = std::move(resize_texture);
  }
  blit_pass->EncodeCommands();
  if (!context->GetCommandQueue()
           ->Submit(
               {command_buffer},
               [](impeller::CommandBuffer::Status status) {
                 if (status == impeller::CommandBuffer::Status::kError) {
                   FML_LOG(ERROR)
                       << "GPU Error submitting image decoding command buffer.";
                 }
               },
               /*block_on_schedule=*/true)
           .ok()) {
    std::string decode_error("Failed to submit image decoding command buffer.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }

  // Flush the pending command buffer to ensure that its output becomes visible
  // to the raster thread.
  if (context->AddTrackingFence(result_texture)) {
    command_buffer->WaitUntilScheduled();
  } else {
    command_buffer->WaitUntilCompleted();
  }

  context->DisposeThreadLocalCachedResources();

  return std::make_pair(
      impeller::DlImageImpeller::Make(std::move(result_texture)),
      std::string());
}

void ImageDecoderImpeller::UploadTextureToPrivate(
    ImageResult result,
    const std::shared_ptr<impeller::Context>& context,
    const std::shared_ptr<impeller::DeviceBuffer>& buffer,
    const ImageDecoderImpeller::ImageInfo& image_info,
    const std::optional<SkImageInfo>& resize_info,
    const std::shared_ptr<const fml::SyncSwitch>& gpu_disabled_switch) {
  TRACE_EVENT0("impeller", __FUNCTION__);
  if (!context) {
    result(nullptr, "No Impeller context is available");
    return;
  }
  if (!buffer) {
    result(nullptr, "No Impeller device buffer is available");
    return;
  }

  gpu_disabled_switch->Execute(
      fml::SyncSwitch::Handlers()
          .SetIfFalse([&result, context, buffer, image_info, resize_info] {
            sk_sp<DlImage> image;
            std::string decode_error;
            std::tie(image, decode_error) = std::tie(image, decode_error) =
                UnsafeUploadTextureToPrivate(context, buffer, image_info,
                                             resize_info);
            result(image, decode_error);
          })
          .SetIfTrue([&result, context, buffer, image_info, resize_info] {
            auto result_ptr = std::make_shared<ImageResult>(std::move(result));
            context->StoreTaskForGPU(
                [result_ptr, context, buffer, image_info, resize_info]() {
                  sk_sp<DlImage> image;
                  std::string decode_error;
                  std::tie(image, decode_error) = UnsafeUploadTextureToPrivate(
                      context, buffer, image_info, resize_info);
                  (*result_ptr)(image, decode_error);
                },
                [result_ptr]() {
                  (*result_ptr)(
                      nullptr,
                      "Image upload failed due to loss of GPU access.");
                });
          }));
}

std::pair<sk_sp<DlImage>, std::string>
ImageDecoderImpeller::UploadTextureToStorage(
    const std::shared_ptr<impeller::Context>& context,
    std::shared_ptr<SkBitmap> bitmap) {
  TRACE_EVENT0("impeller", __FUNCTION__);
  if (!context) {
    return std::make_pair(nullptr, "No Impeller context is available");
  }
  if (!bitmap) {
    return std::make_pair(nullptr, "No texture bitmap is available");
  }
  const auto image_info = bitmap->info();
  const auto pixel_format = ToPixelFormat(image_info.colorType());
  if (!pixel_format) {
    std::string decode_error(
        std::format("Unsupported pixel format (SkColorType={})",
                    static_cast<int>(image_info.colorType())));
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }

  impeller::TextureDescriptor texture_descriptor;
  texture_descriptor.storage_mode = impeller::StorageMode::kHostVisible;
  texture_descriptor.format = pixel_format.value();
  texture_descriptor.size = {image_info.width(), image_info.height()};
  texture_descriptor.mip_count = 1;

  auto texture =
      context->GetResourceAllocator()->CreateTexture(texture_descriptor);
  if (!texture) {
    std::string decode_error("Could not create Impeller texture.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }

  auto mapping = std::make_shared<fml::NonOwnedMapping>(
      reinterpret_cast<const uint8_t*>(bitmap->getAddr(0, 0)),  // data
      texture_descriptor.GetByteSizeOfBaseMipLevel(),           // size
      [bitmap](auto, auto) mutable { bitmap.reset(); }          // proc
  );

  if (!texture->SetContents(mapping)) {
    std::string decode_error("Could not copy contents into Impeller texture.");
    FML_DLOG(ERROR) << decode_error;
    return std::make_pair(nullptr, decode_error);
  }

  texture->SetLabel(
      std::format("ui.Image({})", static_cast<const void*>(texture.get()))
          .c_str());

  context->DisposeThreadLocalCachedResources();

  return std::make_pair(impeller::DlImageImpeller::Make(std::move(texture)),
                        std::string());
}

// |ImageDecoder|
void ImageDecoderImpeller::Decode(fml::RefPtr<ImageDescriptor> descriptor,
                                  const ImageDecoder::Options& options,
                                  const ImageResult& p_result) {
  FML_DCHECK(descriptor);
  FML_DCHECK(p_result);

  // Wrap the result callback so that it can be invoked from any thread.
  auto raw_descriptor = descriptor.get();
  raw_descriptor->AddRef();
  ImageResult result = [p_result,                               //
                        raw_descriptor,                         //
                        ui_runner = runners_.GetUITaskRunner()  //
  ](const auto& image, const auto& decode_error) {
    ui_runner->PostTask([raw_descriptor, p_result, image, decode_error]() {
      raw_descriptor->Release();
      p_result(std::move(image), decode_error);
    });
  };

  concurrent_task_runner_->PostTask(
      [raw_descriptor,            //
       context = context_.get(),  //
       options,
       io_runner = runners_.GetIOTaskRunner(),  //
       result,
       wide_gamut_enabled = wide_gamut_enabled_,  //
       gpu_disabled_switch = gpu_disabled_switch_]() {
#if FML_OS_IOS_SIMULATOR
        // No-op backend.
        if (!context) {
          return;
        }
#endif  // FML_OS_IOS_SIMULATOR

        if (!context) {
          result(nullptr, "No Impeller context is available");
          return;
        }
        auto max_size_supported =
            context->GetResourceAllocator()->GetMaxTextureSizeSupported();

        // Always decompress on the concurrent runner.
        auto bitmap_result = DecompressTexture(
            raw_descriptor, options, max_size_supported,
            /*supports_wide_gamut=*/wide_gamut_enabled &&
                context->GetCapabilities()->SupportsExtendedRangeFormats(),
            context->GetCapabilities(), context->GetResourceAllocator());
        if (!bitmap_result.ok()) {
          result(nullptr, std::string(bitmap_result.status().message()));
          return;
        }

        auto upload_texture_and_invoke_result = [result, context, bitmap_result,
                                                 gpu_disabled_switch]() {
          UploadTextureToPrivate(result, context,               //
                                 bitmap_result->device_buffer,  //
                                 bitmap_result->image_info,     //
                                 bitmap_result->resize_info,    //
                                 gpu_disabled_switch            //
          );
        };
        // The I/O image uploads are not threadsafe on GLES.
        if (context->GetBackendType() ==
            impeller::Context::BackendType::kOpenGLES) {
          io_runner->PostTask(upload_texture_and_invoke_result);
        } else {
          upload_texture_and_invoke_result();
        }
      });
}

ImpellerAllocator::ImpellerAllocator(
    std::shared_ptr<impeller::Allocator> allocator)
    : allocator_(std::move(allocator)) {}

std::shared_ptr<impeller::DeviceBuffer> ImpellerAllocator::GetDeviceBuffer()
    const {
  return buffer_;
}

bool ImpellerAllocator::allocPixelRef(SkBitmap* bitmap) {
  if (!bitmap) {
    return false;
  }
  const SkImageInfo& info = bitmap->info();
  if (kUnknown_SkColorType == info.colorType() || info.width() < 0 ||
      info.height() < 0 || !info.validRowBytes(bitmap->rowBytes())) {
    return false;
  }

  impeller::DeviceBufferDescriptor descriptor;
  descriptor.storage_mode = impeller::StorageMode::kHostVisible;
  descriptor.size = ((bitmap->height() - 1) * bitmap->rowBytes()) +
                    (bitmap->width() * bitmap->bytesPerPixel());

  std::shared_ptr<impeller::DeviceBuffer> device_buffer =
      allocator_->CreateBuffer(descriptor);
  if (!device_buffer) {
    return false;
  }

  struct ImpellerPixelRef final : public SkPixelRef {
    ImpellerPixelRef(int w, int h, void* s, size_t r)
        : SkPixelRef(w, h, s, r) {}

    ~ImpellerPixelRef() override {}
  };

  auto pixel_ref = sk_sp<SkPixelRef>(
      new ImpellerPixelRef(info.width(), info.height(),
                           device_buffer->OnGetContents(), bitmap->rowBytes()));

  bitmap->setPixelRef(std::move(pixel_ref), 0, 0);
  buffer_ = std::move(device_buffer);
  return true;
}

}  // namespace flutter
