// Copyright 2020 The Chromium 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 "cc/paint/clear_for_opaque_raster.h"

#include <cmath>

#include "base/check_op.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/vector2d_f.h"

namespace cc {

bool CalculateClearForOpaqueRasterRects(const gfx::Vector2dF& translation,
                                        const gfx::SizeF& scale,
                                        const gfx::Size& content_size,
                                        const gfx::Rect& canvas_bitmap_rect,
                                        const gfx::Rect& canvas_playback_rect,
                                        gfx::Rect& outer_rect,
                                        gfx::Rect& inner_rect) {
  // If there is translation, the top and/or left texels are not guaranteed to
  // be fully opaque.
  DCHECK_GE(translation.x(), 0.0f);
  DCHECK_GE(translation.y(), 0.0f);
  DCHECK_LT(translation.x(), 1.0f);
  DCHECK_LT(translation.y(), 1.0f);
  bool left_opaque = translation.x() == 0.0f;
  bool top_opaque = translation.y() == 0.0f;
  // If there is scale, the right and/or bottom texels are not guaranteed to be
  // fully opaque.
  bool right_opaque = scale.width() == 1.0f;
  bool bottom_opaque = scale.height() == 1.0f;
  if (left_opaque && top_opaque && right_opaque && bottom_opaque)
    return false;

  // |outer_rect| is the bounds of all texels affected by content.
  outer_rect = gfx::Rect(content_size);
  // |inner_rect| is the opaque coverage of the content.
  inner_rect = outer_rect;
  // If not fully covered, one texel inside the content rect may not be opaque
  // (because of blending during raster) and, for scale, one texel outside
  // (because of bilinear filtering during draw) may not be opaque.
  outer_rect.Inset(0, 0, right_opaque ? 0 : -1, bottom_opaque ? 0 : -1);
  inner_rect.Inset(left_opaque ? 0 : 1, top_opaque ? 0 : 1,
                   right_opaque ? 0 : 1, bottom_opaque ? 0 : 1);

  // If the playback rect is touching either edge of the content rect, extend it
  // by one to include the extra texel outside that was added to outer_rect
  // above.
  bool touches_left_edge = !left_opaque && !canvas_playback_rect.x();
  bool touches_top_edge = !top_opaque && !canvas_playback_rect.y();
  bool touches_right_edge =
      !right_opaque && content_size.width() == canvas_playback_rect.right();
  bool touches_bottom_edge =
      !bottom_opaque && content_size.height() == canvas_playback_rect.bottom();
  gfx::Rect adjusted_playback_rect = canvas_playback_rect;
  adjusted_playback_rect.Inset(
      touches_left_edge ? -1 : 0, touches_top_edge ? -1 : 0,
      touches_right_edge ? -1 : 0, touches_bottom_edge ? -1 : 0);

  // No need to clear if the playback area is fully covered by the opaque
  // content.
  if (inner_rect.Contains(adjusted_playback_rect))
    return false;

  outer_rect.Intersect(adjusted_playback_rect);
  DCHECK(!outer_rect.IsEmpty());
  inner_rect.Intersect(adjusted_playback_rect);
  // inner_rect can be empty if the content is very small.

  // Move the rects into the device space.
  outer_rect.Offset(-canvas_bitmap_rect.OffsetFromOrigin());
  inner_rect.Offset(-canvas_bitmap_rect.OffsetFromOrigin());
  return inner_rect != outer_rect;
}

}  // namespace cc
