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 "ash/capture_mode/capture_region_overlay_controller.h"

#include <optional>
#include <string>
#include <utility>

#include "ash/capture_mode/capture_mode_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/capture_mode/capture_mode_api.h"
#include "ash/scanner/scanner_text.h"
#include "base/time/time.h"
#include "cc/paint/filter_operation.h"
#include "cc/paint/filter_operations.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/render_surface_filters.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/skia_paint_util.h"

namespace ash {

namespace {

// TODO(b/367548979): Use correct style constants for detected text regions.
constexpr SkColor kDetectedTextRegionColor = SK_ColorCYAN;
constexpr float kDetectedTextRegionOpacity = 0.3f;

// TODO(b/367549273): Use correct colors to paint translated text.
constexpr SkColor kTranslatedTextColor = SK_ColorBLACK;
constexpr SkColor kTranslatedTextBackgroundColor = SK_ColorWHITE;

// The duration of the region glow pulse animation.
constexpr base::TimeDelta kRegionGlowPulseDuration = base::Milliseconds(666);

// The minimum and maximum opacity of the region glow pulse animation.
constexpr float kRegionGlowMinOpacity = 0.35f;
constexpr float kRegionGlowMaxOpacity = 1.0f;

// Translates and rotates `canvas` so that `center_rotated_box` is upright and
// centered on the canvas. The components of `center_rotated_box` should be
// specified as coordinates relative to `region`.
void TranslateAndRotateCanvas(
    gfx::Canvas& canvas,
    const gfx::Rect& region,
    const ScannerText::CenterRotatedBox& center_rotated_box) {
  canvas.Translate(center_rotated_box.center.OffsetFromOrigin() +
                   region.origin().OffsetFromOrigin());
  canvas.sk_canvas()->rotate(center_rotated_box.rotation);
}

// Gets a rect with size `size` centered at the origin (offset to the nearest
// integer coordinates if `size` has odd width or odd height).
gfx::Rect GetRectCenteredAtOrigin(const gfx::Size& size) {
  return gfx::Rect(-size.width() / 2, -size.height() / 2, size.width(),
                   size.height());
}

}  // namespace

CaptureRegionOverlayController::CaptureRegionOverlayController() {
  DCHECK(CanShowSunfishOrScannerUi());
}

CaptureRegionOverlayController::~CaptureRegionOverlayController() = default;

void CaptureRegionOverlayController::OnTextDetected(
    std::optional<ScannerText> detected_text) {
  detected_text_ = std::move(detected_text);
}

void CaptureRegionOverlayController::OnTranslatedTextFetched(
    std::optional<ScannerText> translated_text) {
  translated_text_ = std::move(translated_text);
}

void CaptureRegionOverlayController::PaintCaptureRegionOverlay(
    gfx::Canvas& canvas,
    const gfx::Rect& region_bounds_in_canvas) const {
  PaintDetectedTextRegions(canvas, region_bounds_in_canvas);
  PaintTranslatedText(canvas, region_bounds_in_canvas);
}

void CaptureRegionOverlayController::StartGlowAnimation(
    gfx::AnimationDelegate* animation_delegate) {
  if (!glow_animation_) {
    glow_animation_ = std::make_unique<gfx::ThrobAnimation>(animation_delegate);
    glow_animation_->SetThrobDuration(kRegionGlowPulseDuration);
    glow_animation_->SetTweenType(gfx::Tween::LINEAR);
  }
  // Set `cycles_til_stop` to be negative so that the animation continues
  // indefinitely.
  glow_animation_->StartThrobbing(/*cycles_til_stop=*/-1);
}

void CaptureRegionOverlayController::PauseGlowAnimation() {
  if (glow_animation_) {
    // Complete the current animation cycle then remain there. This will pause
    // the glow animation at the end of the cycle, where the glow has minimum
    // outset and blur.
    glow_animation_->set_cycles_remaining(0);
  }
}

void CaptureRegionOverlayController::RemoveGlowAnimation() {
  glow_animation_ = nullptr;
}

bool CaptureRegionOverlayController::HasGlowAnimation() const {
  return glow_animation_ != nullptr;
}

void CaptureRegionOverlayController::PaintCurrentGlowState(
    gfx::Canvas& canvas,
    const gfx::Rect& region_bounds_in_canvas,
    const ui::ColorProvider* color_provider) const {
  if (!glow_animation_) {
    return;
  }

  gfx::Rect current_glow_bounds(region_bounds_in_canvas);
  current_glow_bounds.Outset(glow_animation_->CurrentValueBetween(
      capture_mode::kRegionGlowMinOutsetDp,
      capture_mode::kRegionGlowMaxOutsetDp));
  cc::PaintFlags flags;
  flags.setAlphaf(static_cast<float>(glow_animation_->CurrentValueBetween(
      kRegionGlowMinOpacity, kRegionGlowMaxOpacity)));
  flags.setShader(gfx::CreateGradientShader(
      current_glow_bounds.origin(), current_glow_bounds.top_right(),
      color_provider->GetColor(cros_tokens::kCrosSysMuted),
      color_provider->GetColor(cros_tokens::kCrosSysComplement)));
  flags.setImageFilter(cc::RenderSurfaceFilters::BuildImageFilter(
      cc::FilterOperations({cc::FilterOperation::CreateBlurFilter(
          glow_animation_->CurrentValueBetween(
              capture_mode::kRegionGlowAnimationMinBlurDp,
              capture_mode::kRegionGlowAnimationMaxBlurDp))})));
  canvas.DrawRect(current_glow_bounds, flags);
}

void CaptureRegionOverlayController::PaintDetectedTextRegions(
    gfx::Canvas& canvas,
    const gfx::Rect& region_bounds_in_canvas) const {
  if (!detected_text_.has_value()) {
    return;
  }

  // TODO(b/367548979): Paint detected text regions with correct styling.
  cc::PaintFlags flags;
  flags.setStyle(cc::PaintFlags::kFill_Style);
  flags.setColor(kDetectedTextRegionColor);
  flags.setAlphaf(kDetectedTextRegionOpacity);
  for (const ScannerText::Paragraph& paragraph : detected_text_->paragraphs()) {
    for (const ScannerText::Line& line : paragraph.lines()) {
      canvas.Save();
      TranslateAndRotateCanvas(canvas, region_bounds_in_canvas,
                               line.bounding_box());
      canvas.DrawRect(GetRectCenteredAtOrigin(line.bounding_box().size), flags);
      canvas.Restore();
    }
  }
}

void CaptureRegionOverlayController::PaintTranslatedText(
    gfx::Canvas& canvas,
    const gfx::Rect& region_bounds_in_canvas) const {
  if (!translated_text_.has_value()) {
    return;
  }

  // TODO(b/367549273): Paint translated text with correct styling.
  cc::PaintFlags flags;
  flags.setStyle(cc::PaintFlags::kFill_Style);
  flags.setColor(kTranslatedTextBackgroundColor);
  for (const ScannerText::Paragraph& paragraph :
       translated_text_->paragraphs()) {
    for (const ScannerText::Line& line : paragraph.lines()) {
      canvas.Save();
      TranslateAndRotateCanvas(canvas, region_bounds_in_canvas,
                               line.bounding_box());
      const gfx::Rect centered_bounds =
          GetRectCenteredAtOrigin(line.bounding_box().size);
      canvas.DrawRect(centered_bounds, flags);
      canvas.DrawStringRect(translated_text_->GetTextFromRange(line.range()),
                            gfx::FontList(), kTranslatedTextColor,
                            centered_bounds);
      canvas.Restore();
    }
  }
}

}  // namespace ash