910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "skia/ext/draw_gainmap_image.h"

#include "base/check_op.h"
#include "base/logging.h"
#include "skia/ext/geometry.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkShader.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/ganesh/GrRecordingContext.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/graphite/Surface.h"
#include "third_party/skia/include/private/SkGainmapInfo.h"
#include "third_party/skia/include/private/SkGainmapShader.h"

namespace skia {

// Resample `gain_image` so that it has the same dimensions as `base_image`.
// Update `gain_rect` so that it has the same relationship to the new bounds
// of `gain_image` as it did to the old bounds. On failure, leave `gain_image`
// and `gain_rect` unchanged.
void ResampleGainmap(sk_sp<SkImage> base_image,
                     SkRect base_rect,
                     sk_sp<SkImage>& gain_image,
                     SkRect& gain_rect,
                     GrRecordingContext* context,
                     skgpu::graphite::Recorder* recorder) {
  SkImageInfo surface_info = gain_image->imageInfo()
                                 .makeDimensions(base_image->dimensions())
                                 .makeColorSpace(nullptr);
  sk_sp<SkSurface> surface;
#if defined(SK_GANESH)
  if (context) {
    surface =
        SkSurfaces::RenderTarget(context, skgpu::Budgeted::kNo, surface_info,
                                 /*sampleCount=*/0, kTopLeft_GrSurfaceOrigin,
                                 /*surfaceProps=*/nullptr,
                                 /*shouldCreateWithMips=*/false);
  }
#endif
#if defined(SK_GRAPHITE)
  if (recorder) {
    surface =
        SkSurfaces::RenderTarget(recorder, surface_info, skgpu::Mipmapped::kNo,
                                 /*surfaceProps=*/nullptr);
  }
#endif
  if (!context && !recorder) {
    surface = SkSurfaces::Raster(surface_info, surface_info.minRowBytes(),
                                 /*surfaceProps=*/nullptr);
  }
  if (!surface) {
    return;
  }

  SkRect dst_rect = SkRect::MakeSize(SkSize::Make(surface_info.dimensions()));
  SkRect src_rect = ScaleSkRectProportional(gain_rect, base_rect, dst_rect);
  surface->getCanvas()->drawImageRect(
      gain_image, src_rect, dst_rect, SkSamplingOptions(SkFilterMode::kLinear),
      nullptr, SkCanvas::kStrict_SrcRectConstraint);
  auto resampled_gain_image = surface->makeImageSnapshot();
  if (!resampled_gain_image) {
    return;
  }

  gain_image = resampled_gain_image;
  gain_rect = base_rect;
}

void DrawGainmapImageRect(SkCanvas* canvas,
                          sk_sp<SkImage> base_image,
                          sk_sp<SkImage> gain_image,
                          const SkGainmapInfo& gainmap_info,
                          float hdr_headroom,
                          const SkRect& source_rect,
                          const SkRect& dest_rect,
                          const SkSamplingOptions& sampling,
                          const SkPaint& paint) {
  // Compute `dest_rect_clipped` to be intersected with the pre-image of the
  // clip rect of `canvas`.
  SkRect dest_rect_clipped = dest_rect;
  const SkMatrix& dest_to_device = canvas->getTotalMatrix();
  SkMatrix device_to_dest;
  if (dest_to_device.invert(&device_to_dest)) {
    SkRect dest_clip_rect;
    device_to_dest.mapRect(&dest_clip_rect,
                           SkRect::Make(canvas->getDeviceClipBounds()));
    dest_rect_clipped.intersect(dest_clip_rect);
  }

  // Compute the input rect for the base and gainmap images.
  SkRect base_rect =
      skia::ScaleSkRectProportional(source_rect, dest_rect, dest_rect_clipped);
  SkRect gain_rect = skia::ScaleSkRectProportional(
      SkRect::Make(gain_image->bounds()), SkRect::Make(base_image->bounds()),
      base_rect);

  auto* context = canvas->recordingContext();
  auto* recorder = canvas->recorder();

  // Compute a tiling that ensures that the base and gainmap images fit on the
  // GPU.
  const std::vector<SkRect> source_rects = {base_rect, gain_rect};
  const std::vector<sk_sp<SkImage>> source_images = {base_image, gain_image};
  int max_texture_size = 0;
  if (context) {
    max_texture_size = context->maxTextureSize();
  } else if (recorder) {
    // TODO(b/279234024): Retrieve correct max texture size for graphite.
    max_texture_size = 8192;
  }
  skia::Tiling tiling(dest_rect_clipped, source_rects, source_images,
                      max_texture_size);

  // Draw tile-by-tile.
  for (int tx = 0; tx < tiling.GetTileCountX(); ++tx) {
    for (int ty = 0; ty < tiling.GetTileCountY(); ++ty) {
      // Retrieve the tile geometry.
      SkRect tile_dest_rect;
      std::vector<SkRect> tile_source_rects;
      std::vector<std::optional<SkIRect>> tile_subset_rects;
      tiling.GetTileRect(tx, ty, tile_dest_rect, tile_source_rects,
                         tile_subset_rects);

      // Extract the images' subsets (if needed).
      auto tile_source_images = source_images;
      for (size_t i = 0; i < 2; ++i) {
        if (!tile_subset_rects[i].has_value()) {
          continue;
        }
        if (tile_subset_rects[i]->isEmpty()) {
          tile_source_images[i] = nullptr;
        } else {
          tile_source_images[i] = tile_source_images[i]->makeSubset(
              nullptr, tile_subset_rects[i].value(), {});
        }
      }

      // When nearnest-neighbor filtering is requested, first resample the
      // gainmap image to have the same size as the base image using linear
      // filtering. See b/341758170.
      if (sampling.filter == SkFilterMode::kNearest &&
          tile_source_rects[0] != tile_source_rects[1]) {
        ResampleGainmap(tile_source_images[0], tile_source_rects[0],
                        tile_source_images[1], tile_source_rects[1], context,
                        recorder);
      }

      // SkGainmapShader will internally use SkImage::makeRawShader, which does
      // not support cubic filtering. Disable cubic filtering here.
      // https://crbug.com/374783345
      std::vector<SkSamplingOptions> tile_sampling = {sampling, sampling};
      for (size_t i = 0; i < 2; ++i) {
        if (!tile_source_images[i] || !tile_sampling[i].useCubic) {
          continue;
        }
        tile_sampling[i] = SkSamplingOptions(SkFilterMode::kLinear,
                                             tile_source_images[i]->hasMipmaps()
                                                 ? SkMipmapMode::kLinear
                                                 : SkMipmapMode::kNone);
      }

      // Draw the tile.
      SkPaint tile_paint = paint;
      auto shader = SkGainmapShader::Make(
          tile_source_images[0], tile_source_rects[0], tile_sampling[0],
          tile_source_images[1], tile_source_rects[1], tile_sampling[1],
          gainmap_info, tile_dest_rect, hdr_headroom);
      tile_paint.setShader(std::move(shader));
      canvas->drawRect(tile_dest_rect, tile_paint);
    }
  }
}

void DrawGainmapImage(SkCanvas* canvas,
                      sk_sp<SkImage> base_image,
                      sk_sp<SkImage> gainmap_image,
                      const SkGainmapInfo& gainmap_info,
                      float hdr_headroom,
                      SkScalar left,
                      SkScalar top,
                      const SkSamplingOptions& sampling,
                      const SkPaint& paint) {
  SkRect source_rect = SkRect::Make(base_image->bounds());
  SkRect dest_rect =
      SkRect::MakeXYWH(left, top, base_image->width(), base_image->height());
  DrawGainmapImageRect(canvas, std::move(base_image), std::move(gainmap_image),
                       gainmap_info, hdr_headroom, source_rect, dest_rect,
                       sampling, paint);
}

}  // namespace skia