chromium/ios/chrome/browser/ui/scanner/preview_overlay_view.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/scanner/preview_overlay_view.h"

#import <AVFoundation/AVFoundation.h>

#import "ios/chrome/common/ui/util/ui_util.h"
#import "ui/base/device_form_factor.h"

namespace {

// Length of the viewport borders, starting from the corner.
const CGFloat kViewportBorderLengthFromCornerIPhone = 25.0;
const CGFloat kViewportBorderLengthFromCornerIPad = 30.0;

// Opacity of the preview overlay.
const CGFloat kPreviewOverlayOpacity = 0.5;

// Corner radius of the border around the viewport.
const CGFloat kViewportBorderCornerRadius = 2.0;
// Line width of the viewport border.
const CGFloat kViewportBorderLineWidth = 4.0;
// Shadow opacity of the viewport border.
const CGFloat kViewportBorderShadowOpacity = 1.0;
// Shadow radius of the viewport border.
const CGFloat kViewportBorderShadowRadius = 10.0;

}  // namespace

@interface PreviewOverlayView () {
  // A transparent preview overlay. The overlay is a sublayer of the
  // PreviewOverlayView's view to keep the opacity of the view's layer 1.0,
  // otherwise the viewport border would inherit the opacity of the overlay.
  CALayer* _previewOverlay;
  // A container for the viewport border to draw a shadow under the border.
  // Sublayer of PreviewOverlayView's layer.
  CALayer* _viewportBorderContainer;
  // The preview viewport border. Sublayer of `_viewportBorderContainer`.
  CAShapeLayer* _viewportBorder;
  // The current viewport size.
  CGSize _viewportSize;
}
@end

@implementation PreviewOverlayView

- (instancetype)initWithFrame:(CGRect)frame
                 viewportSize:(CGSize)overlayViewportSize {
  self = [super initWithFrame:frame];
  if (!self) {
    return nil;
  }

  _viewportSize = overlayViewportSize;

  _previewOverlay = [[CALayer alloc] init];
  [_previewOverlay setBackgroundColor:[[UIColor blackColor] CGColor]];
  [_previewOverlay setOpacity:kPreviewOverlayOpacity];
  [[self layer] addSublayer:_previewOverlay];

  _viewportBorderContainer = [[CALayer alloc] init];
  [_viewportBorderContainer setShadowColor:[[UIColor blackColor] CGColor]];
  [_viewportBorderContainer setShadowOffset:CGSizeZero];
  [_viewportBorderContainer setShadowRadius:kViewportBorderShadowRadius];
  [_viewportBorderContainer setShadowOpacity:kViewportBorderShadowOpacity];
  [_viewportBorderContainer setShouldRasterize:YES];
  [_viewportBorderContainer
      setRasterizationScale:[[UIScreen mainScreen] scale]];

  _viewportBorder = [[CAShapeLayer alloc] init];
  [_viewportBorder setStrokeColor:[[UIColor whiteColor] CGColor]];
  [_viewportBorder setFillColor:nil];
  [_viewportBorder setOpacity:1.0];
  [_viewportBorder setLineWidth:kViewportBorderLineWidth];
  [_viewportBorderContainer addSublayer:_viewportBorder];

  [[self layer] addSublayer:_viewportBorderContainer];
  return self;
}

- (void)layoutSubviews {
  [super layoutSubviews];
  CGSize frameSize = self.frame.size;

  CGFloat viewportWidth = _viewportSize.width;
  CGFloat viewportHeight = _viewportSize.height;

  [_previewOverlay
      setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)];
  [_previewOverlay setMask:[self viewportMaskWithFrameSize:frameSize]];

  CGRect borderRect = CGRectMakeCenteredRectInFrame(
      frameSize, CGSizeMake(viewportWidth + kViewportBorderLineWidth,
                            viewportHeight + kViewportBorderLineWidth));
  UIBezierPath* borderPath =
      [UIBezierPath bezierPathWithRoundedRect:borderRect
                                 cornerRadius:kViewportBorderCornerRadius];

  [_viewportBorder setPath:[borderPath CGPath]];
  [_viewportBorder setMask:[self viewportBorderMaskWithFrameSize:frameSize]];
}

#pragma mark - Private

// Creates a square mask for the overlay to keep the viewport transparent.
- (CAShapeLayer*)viewportMaskWithFrameSize:(CGSize)frameSize {
  CGRect frameRect = CGRectMake(0, 0, frameSize.width, frameSize.height);
  CGRect viewportRect = CGRectMakeCenteredRectInFrame(frameSize, _viewportSize);
  UIBezierPath* maskPath = [UIBezierPath bezierPathWithRect:frameRect];
  [maskPath appendPath:[UIBezierPath bezierPathWithRect:viewportRect]];

  CAShapeLayer* mask = [[CAShapeLayer alloc] init];
  [mask setFillColor:[[UIColor blackColor] CGColor]];
  [mask setFillRule:kCAFillRuleEvenOdd];
  [mask setFrame:frameRect];
  [mask setPath:maskPath.CGPath];
  return mask;
}

// Creates a mask to only draw the corners of the viewport border.
- (CAShapeLayer*)viewportBorderMaskWithFrameSize:(CGSize)frameSize {
  CGFloat viewportBorderCornerLength =
      (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET)
          ? kViewportBorderLengthFromCornerIPad
          : kViewportBorderLengthFromCornerIPhone;
  CGRect maskRect = CGRectMakeCenteredRectInFrame(
      frameSize,
      CGSizeMake(_viewportSize.width - 2 * viewportBorderCornerLength,
                 _viewportSize.height - 2 * viewportBorderCornerLength));
  CGFloat sizeX = maskRect.origin.x;
  CGFloat sizeY = maskRect.origin.y;
  CGFloat offsetX = sizeX + maskRect.size.width;
  CGFloat offsetY = sizeY + maskRect.size.height;

  UIBezierPath* path =
      [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, sizeX, sizeY)];
  [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(0, offsetY,
                                                               sizeX, sizeY)]];
  [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetX, 0,
                                                               sizeX, sizeY)]];
  [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetX, offsetY,
                                                               sizeX, sizeY)]];

  CAShapeLayer* mask = [[CAShapeLayer alloc] init];
  [mask setFillColor:[[UIColor blackColor] CGColor]];
  [mask setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)];
  [mask setPath:path.CGPath];
  return mask;
}

@end