diff --git a/client/ios/Core/BaseUI/FilterProtocol.swift b/client/ios/Core/Base/FilterProtocol.swift similarity index 64% rename from client/ios/Core/BaseUI/FilterProtocol.swift rename to client/ios/Core/Base/FilterProtocol.swift index ea85e9d34..c955a53e5 100644 --- a/client/ios/Core/BaseUI/FilterProtocol.swift +++ b/client/ios/Core/Base/FilterProtocol.swift @@ -1,3 +1,5 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + protocol FilterProtocol { var name: String { get } var parameters: [String: Any] { get } diff --git a/client/ios/Core/Base/ImageBlur.swift b/client/ios/Core/Base/ImageBlur.swift new file mode 100644 index 000000000..cb567caad --- /dev/null +++ b/client/ios/Core/Base/ImageBlur.swift @@ -0,0 +1,31 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + +import CoreImage + +public enum ImageBlurType: FilterProtocol { + case gaussian(radius: CGFloat) + + public var name: String { + switch self { + case .gaussian: + return "CIGaussianBlur" + } + } + + public var parameters: [String: Any] { + switch self { + case let .gaussian(radius): + return [kCIInputRadiusKey: radius] + } + } + + public var imageFilter: ImageFilter { + { image in + let parameters: [String: Any] = [ + kCIInputImageKey: image, + ] + let filter = CIFilter(name: name, parameters: self.parameters.merging(parameters) { $1 }) + return filter?.outputImage + } + } +} diff --git a/client/ios/Core/BaseUI/ImageComposer.swift b/client/ios/Core/Base/ImageComposer.swift similarity index 58% rename from client/ios/Core/BaseUI/ImageComposer.swift rename to client/ios/Core/Base/ImageComposer.swift index 7d681f5d6..2f2691469 100644 --- a/client/ios/Core/BaseUI/ImageComposer.swift +++ b/client/ios/Core/Base/ImageComposer.swift @@ -1,18 +1,35 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + import CoreImage public enum ImageComposerType: FilterProtocol { case sourceAtop + case sourceIn + case darken + case lighten + case multiply + case screen public var name: String { switch self { case .sourceAtop: return "CISourceAtopCompositing" + case .sourceIn: + return "CISourceInCompositing" + case .darken: + return "CIDarkenBlendMode" + case .lighten: + return "CILightenBlendMode" + case .multiply: + return "CIMultiplyCompositing" + case .screen: + return "CIScreenBlendMode" } } public var parameters: [String: Any] { switch self { - case .sourceAtop: + case .sourceAtop, .sourceIn, .darken, .lighten, .multiply, .screen: return [:] } } diff --git a/client/ios/Core/Base/ImageCrop.swift b/client/ios/Core/Base/ImageCrop.swift new file mode 100644 index 000000000..74f3c1299 --- /dev/null +++ b/client/ios/Core/Base/ImageCrop.swift @@ -0,0 +1,31 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + +import CoreImage + +public enum ImageCropType: FilterProtocol { + case crop(rect: CIVector) + + public var name: String { + switch self { + case .crop: + return "CICrop" + } + } + + public var parameters: [String: Any] { + switch self { + case let .crop(rect): + return ["inputRectangle": rect] + } + } + + public var imageFilter: ImageFilter { + { image in + let parameters: [String: Any] = [ + kCIInputImageKey: image, + ] + let filter = CIFilter(name: name, parameters: self.parameters.merging(parameters) { $1 }) + return filter?.outputImage + } + } +} diff --git a/client/ios/Core/Base/ImageEffect.swift b/client/ios/Core/Base/ImageEffect.swift new file mode 100644 index 000000000..a47527009 --- /dev/null +++ b/client/ios/Core/Base/ImageEffect.swift @@ -0,0 +1,10 @@ +// Copyright 2021 Yandex LLC. All rights reserved. + +import CoreGraphics + +import BaseTiny + +public enum ImageEffect: Equatable { + case blur(radius: CGFloat) + case tint(color: RGBAColor, mode: TintMode?) +} diff --git a/client/ios/Core/Base/ImageFilters.swift b/client/ios/Core/Base/ImageFilters.swift new file mode 100644 index 000000000..168fa66ab --- /dev/null +++ b/client/ios/Core/Base/ImageFilters.swift @@ -0,0 +1,22 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + +import CoreImage + +public typealias ImageFilter = (CIImage) -> CIImage? +public typealias ImageGenerator = () -> CIImage? +public typealias ImageComposer = (CIImage) -> ImageFilter + +public func combine( + _ f1: @escaping ImageFilter, + _ f2: @escaping ImageFilter +) -> ImageFilter { + { image in f1(image).flatMap { f2($0) } } +} + +public func combine( + _ f1: @escaping ImageFilter, + _ f2: @escaping ImageFilter, + _ f3: @escaping ImageFilter +) -> ImageFilter { + combine(combine(f1, f2), f3) +} diff --git a/client/ios/Core/BaseUI/ImageGenerator.swift b/client/ios/Core/Base/ImageGenerator.swift similarity index 91% rename from client/ios/Core/BaseUI/ImageGenerator.swift rename to client/ios/Core/Base/ImageGenerator.swift index ac5a54f63..6f52120cf 100644 --- a/client/ios/Core/BaseUI/ImageGenerator.swift +++ b/client/ios/Core/Base/ImageGenerator.swift @@ -1,3 +1,5 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + import CoreImage public enum ImageGeneratorType: FilterProtocol { diff --git a/client/ios/Core/Base/Lazy.swift b/client/ios/Core/Base/Lazy.swift index 94b13cfe5..267ab8a61 100644 --- a/client/ios/Core/Base/Lazy.swift +++ b/client/ios/Core/Base/Lazy.swift @@ -9,7 +9,7 @@ public final class Lazy { private enum State { case willLoad(getter: Getter, deferredActions: [Action]) - case loading + case loading(deferredActions: [Action]) case loaded(value: T) var value: T? { @@ -74,8 +74,8 @@ public final class Lazy { switch state { case let .willLoad(getter, deferredActions): state = .willLoad(getter: getter, deferredActions: deferredActions + [action]) - case .loading: - assertionFailure() + case let .loading(deferredActions): + state = .loading(deferredActions: deferredActions + [action]) case let .loaded(value): action(value) } @@ -85,8 +85,11 @@ public final class Lazy { ensureIsValidThread() switch state { case let .willLoad(getter, deferredActions): - state = .loading + state = .loading(deferredActions: deferredActions) let value = getter() + guard case let .loading(deferredActions) = state else { + return assertionFailure() + } state = .loaded(value: value) deferredActions.forEach { $0(value) } case .loading, .loaded: diff --git a/client/ios/Core/Base/Observer.swift b/client/ios/Core/Base/Observer.swift index 213b60695..e5980eb2b 100644 --- a/client/ios/Core/Base/Observer.swift +++ b/client/ios/Core/Base/Observer.swift @@ -1,4 +1,5 @@ // Copyright 2018 Yandex LLC. All rights reserved. + public struct Observer { public let action: (T) -> Void diff --git a/client/ios/Core/Base/Property.swift b/client/ios/Core/Base/Property.swift index 88e351619..2edf6ff5e 100644 --- a/client/ios/Core/Base/Property.swift +++ b/client/ios/Core/Base/Property.swift @@ -201,3 +201,18 @@ extension Property: ExpressibleByBooleanLiteral where T == Bool { self.init(initialValue: value) } } + +extension Property: ExpressibleByIntegerLiteral where T == Int { + public init(integerLiteral value: Int) { + self.init(initialValue: value) + } +} + +extension Property: ExpressibleByUnicodeScalarLiteral where T == String {} +extension Property: ExpressibleByExtendedGraphemeClusterLiteral where T == String {} + +extension Property: ExpressibleByStringLiteral where T == String { + public init(stringLiteral value: String) { + self.init(initialValue: value) + } +} diff --git a/client/ios/Core/Base/RadialGradientView.swift b/client/ios/Core/Base/RadialGradientView.swift index 162eb3eff..8587cd2a8 100644 --- a/client/ios/Core/Base/RadialGradientView.swift +++ b/client/ios/Core/Base/RadialGradientView.swift @@ -37,7 +37,6 @@ public final class RadialGradientView: UIView { + [gradient.outerColor.cgColor] gradientLayer.locations = ([0] + gradient.intermediatePoints.map { $0.location } + [1]) as [NSNumber] - } public override func layoutSubviews() { @@ -79,12 +78,20 @@ public final class RadialGradientView: UIView { } radius = min(startPoint.x.nearestDistance / kx, startPoint.y.nearestDistance / ky) case .nearestCorner: - if startPoint.x.isOnBorder && startPoint.y.isOnBorder { + if startPoint.x.isOnBorder, startPoint.y.isOnBorder { return CGPoint(x: startPoint.x + .ulpOfOne, y: startPoint.y + .ulpOfOne) } - radius = sqrt(pow(startPoint.x.nearestDistance / kx, 2) + pow(startPoint.y.nearestDistance / ky, 2)) + radius = + sqrt( + pow(startPoint.x.nearestDistance / kx, 2) + + pow(startPoint.y.nearestDistance / ky, 2) + ) case .farthestCorner: - radius = sqrt(pow(startPoint.x.farthestDistance / kx, 2) + pow(startPoint.y.farthestDistance / ky, 2)) + radius = + sqrt( + pow(startPoint.x.farthestDistance / kx, 2) + + pow(startPoint.y.farthestDistance / ky, 2) + ) case .farthestSide: radius = max(startPoint.x.farthestDistance / kx, startPoint.y.farthestDistance / ky) } @@ -104,24 +111,24 @@ public final class RadialGradientView: UIView { } } -fileprivate extension CGFloat { - var nearest: CGFloat { +extension CGFloat { + fileprivate var nearest: CGFloat { self < 0.5 ? 0 : 1 } - var farthest: CGFloat { + fileprivate var farthest: CGFloat { self > 0.5 ? 0 : 1 } - var nearestDistance: CGFloat { + fileprivate var nearestDistance: CGFloat { abs(self.nearest - self) } - var farthestDistance: CGFloat { + fileprivate var farthestDistance: CGFloat { abs(self.farthest - self) } - var isOnBorder: Bool { + fileprivate var isOnBorder: Bool { self == 0 || self == 1 } } diff --git a/client/ios/Core/Base/SettingProperty.swift b/client/ios/Core/Base/SettingProperty.swift index a18add6fc..055c499ba 100644 --- a/client/ios/Core/Base/SettingProperty.swift +++ b/client/ios/Core/Base/SettingProperty.swift @@ -76,25 +76,26 @@ extension SettingProperty where T: NSCoding { extension SettingProperty where T: Codable { public static func codableStorage(_ storage: KeyValueStorage, key: String) -> Property { - Property(getter: { - guard let object = storage.object(forKey: key) else { return nil } - guard let data = object as? Data else { - assertionFailure() + SettingProperty.storage(storage, key: key).decoded(T.self) + } +} + +extension Property where T == Data? { + public func decoded(_: U.Type) -> Property { + bimap( + get: { $0.flatMap { try? SingleValueDecoder().decode(U.self, from: $0) } }, + set: { + if let newValue = $0 { + do { + return try SingleValueEncoder().encode(newValue) + } catch { + assertionFailure(error.localizedDescription) + } + } + return nil } - return try? SingleValueDecoder().decode(T.self, from: data) - }, setter: { - if let newValue = $0 { - do { - let encodedValue = try SingleValueEncoder().encode(newValue) - storage.set(encodedValue, forKey: key) - } catch { - assertionFailure(error.localizedDescription) - } - } else { - storage.removeObject(forKey: key) - } - }) + ) } } diff --git a/client/ios/Core/Base/Signal.swift b/client/ios/Core/Base/Signal.swift index bb6a30276..ef508351b 100644 --- a/client/ios/Core/Base/Signal.swift +++ b/client/ios/Core/Base/Signal.swift @@ -1,4 +1,5 @@ // Copyright 2018 Yandex LLC. All rights reserved. + import Foundation public struct Signal { diff --git a/client/ios/Core/Base/TintMode.swift b/client/ios/Core/Base/TintMode.swift new file mode 100644 index 000000000..db146fc5e --- /dev/null +++ b/client/ios/Core/Base/TintMode.swift @@ -0,0 +1,10 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + +public enum TintMode { + case sourceIn + case sourceAtop + case darken + case lighten + case multiply + case screen +} diff --git a/client/ios/Core/Base/WeakCollection.swift b/client/ios/Core/Base/WeakCollection.swift index 2c6dbaa87..2cb193f9f 100644 --- a/client/ios/Core/Base/WeakCollection.swift +++ b/client/ios/Core/Base/WeakCollection.swift @@ -42,6 +42,10 @@ public struct WeakCollection { public func contains(_ object: T) -> Bool { array.contains(where: { $0.value === (object as AnyObject) }) } + + public mutating func removeAll() { + array.removeAll() + } } extension WeakCollection: CustomStringConvertible { diff --git a/client/ios/Core/BaseTiny/ArrayBuilder.swift b/client/ios/Core/BaseTiny/ArrayBuilder.swift index bc8da8dc5..e84a895cc 100644 --- a/client/ios/Core/BaseTiny/ArrayBuilder.swift +++ b/client/ios/Core/BaseTiny/ArrayBuilder.swift @@ -15,6 +15,10 @@ public enum ArrayBuilder { element.map { [$0] } ?? [] } + public static func buildExpression(_ component: Component) -> Component { + component + } + public static func buildOptional(_ component: Component?) -> Component { component ?? [] } diff --git a/client/ios/Core/BaseTiny/Clamp.swift b/client/ios/Core/BaseTiny/Clamp.swift index 776fbded9..fabeecce8 100644 --- a/client/ios/Core/BaseTiny/Clamp.swift +++ b/client/ios/Core/BaseTiny/Clamp.swift @@ -42,7 +42,10 @@ extension Comparable { } extension Comparable where Self: Strideable, Self.Stride: SignedInteger { - public func clamp(_ range: Range) -> Self { - clamp(ClosedRange(range)) + public func clamp(_ range: Range) -> Self? { + guard !range.isEmpty else { + return nil + } + return clamp(ClosedRange(range)) } } diff --git a/client/ios/Core/BaseTiny/JSONObject.swift b/client/ios/Core/BaseTiny/JSONObject.swift index 345f3f7a4..2d7ac20c6 100644 --- a/client/ios/Core/BaseTiny/JSONObject.swift +++ b/client/ios/Core/BaseTiny/JSONObject.swift @@ -345,6 +345,25 @@ extension Dictionary where Key == String, Value == JSONObject { } } +extension Dictionary where Key == String, Value == JSONObject { + public func value(atPath path: JSONObject.Path) throws -> JSONObject { + try JSONObject.object(self).value(atPath: path) + } +} + +extension Dictionary where Key == String, Value == JSONObject { + public func flatten() -> JSONDictionary { + reduce(into: [:]) { result, entry in + let (key, value) = entry + if case let .object(dict) = value { + dict.flatten().forEach { result[key + "." + $0] = $1 } + } else { + result[key] = value + } + } + } +} + extension JSONObject { public struct Path: Codable, CustomDebugStringConvertible, ExpressibleByStringLiteral, Hashable { public typealias Component = JSONPathComponent diff --git a/client/ios/Core/BaseUI/AccessibilityElement.swift b/client/ios/Core/BaseUI/AccessibilityElement.swift index 370df9012..ee23628a8 100644 --- a/client/ios/Core/BaseUI/AccessibilityElement.swift +++ b/client/ios/Core/BaseUI/AccessibilityElement.swift @@ -102,9 +102,15 @@ extension AccessibilityElement { public static func button( strings: Strings, + enabled: Bool = true, startsMediaSession: Bool = false ) -> AccessibilityElement { - AccessibilityElement(traits: .button, strings: strings, startsMediaSession: startsMediaSession) + AccessibilityElement( + traits: .button, + strings: strings, + enabled: enabled, + startsMediaSession: startsMediaSession + ) } public static func header(label: String?) -> AccessibilityElement? { diff --git a/client/ios/Core/BaseUI/CGColorExtensions.swift b/client/ios/Core/BaseUI/CGColorExtensions.swift index ccb49aedc..848168376 100644 --- a/client/ios/Core/BaseUI/CGColorExtensions.swift +++ b/client/ios/Core/BaseUI/CGColorExtensions.swift @@ -1,8 +1,11 @@ +// Copyright 2021 Yandex LLC. All rights reserved. + import CoreGraphics -import CoreImage import BaseTiny +@_implementationOnly import CoreImage + extension CGColor { public var rgba: RGBAColor { let color = CIColor(cgColor: self) diff --git a/client/ios/Core/BaseUI/ImageFilters.swift b/client/ios/Core/BaseUI/ImageFilters.swift deleted file mode 100644 index 25a9a24c3..000000000 --- a/client/ios/Core/BaseUI/ImageFilters.swift +++ /dev/null @@ -1,12 +0,0 @@ -import CoreImage - -public typealias ImageFilter = (CIImage) -> CIImage? -public typealias ImageGenerator = () -> CIImage? -public typealias ImageComposer = (CIImage) -> ImageFilter - -public func combine( - _ lhs: @escaping ImageFilter, - _ rhs: @escaping ImageFilter -) -> ImageFilter { - { image in lhs(image).flatMap { rhs($0) } } -} diff --git a/client/ios/Core/BaseUI/UIDeviceExtensions.swift b/client/ios/Core/BaseUI/UIDeviceExtensions.swift index 0fe81c0f0..59c327086 100644 --- a/client/ios/Core/BaseUI/UIDeviceExtensions.swift +++ b/client/ios/Core/BaseUI/UIDeviceExtensions.swift @@ -31,6 +31,8 @@ extension UIDevice { public static let iPhone12mini = "iPhone13,1" public static let iPhone12Pro = "iPhone13,3" public static let iPhone12ProMax = "iPhone13,4" + + public static let iPadAir_5gen = "iPad13,17" } public var systemModelName: String { diff --git a/client/ios/Core/CommonCore/ImageRedrawingStyle.swift b/client/ios/Core/CommonCore/ImageRedrawingStyle.swift new file mode 100644 index 000000000..7d7297ee3 --- /dev/null +++ b/client/ios/Core/CommonCore/ImageRedrawingStyle.swift @@ -0,0 +1,24 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + +import BaseUI + +public struct ImageRedrawingStyle: Equatable { + public static func ==(lhs: ImageRedrawingStyle, rhs: ImageRedrawingStyle) -> Bool { + lhs.tintColor == rhs.tintColor && + lhs.tintMode == rhs.tintMode + } + + let tintColor: Color? + let tintMode: TintMode? + let effects: [ImageEffect] + + public init( + tintColor: Color?, + tintMode: TintMode? = nil, + effects: [ImageEffect] + ) { + self.tintColor = tintColor + self.tintMode = tintMode + self.effects = effects + } +} diff --git a/client/ios/Core/CommonCore/ImageViewProtocol.swift b/client/ios/Core/CommonCore/ImageViewProtocol.swift index d46d1cd00..6d356dcbf 100644 --- a/client/ios/Core/CommonCore/ImageViewProtocol.swift +++ b/client/ios/Core/CommonCore/ImageViewProtocol.swift @@ -1,3 +1,5 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + import UIKit import Base @@ -29,22 +31,3 @@ public struct ImageViewAnimation { self.options = options } } - -public struct ImageRedrawingStyle: Equatable { - let tintColor: Color - let tintMode: TintMode? - - public init(tintColor: Color, tintMode: TintMode? = nil) { - self.tintColor = tintColor - self.tintMode = tintMode - } - - public enum TintMode { - case sourceIn - case sourceAtop - case darken - case lighten - case multiply - case screen - } -} diff --git a/client/ios/Core/CommonCore/MetalImageView.swift b/client/ios/Core/CommonCore/MetalImageView.swift index c0f1af780..aac145540 100644 --- a/client/ios/Core/CommonCore/MetalImageView.swift +++ b/client/ios/Core/CommonCore/MetalImageView.swift @@ -1,3 +1,5 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + import MetalKit import UIKit @@ -18,8 +20,6 @@ public final class MetalImageView: UIView, RemoteImageViewContentProtocol { return mtkView }() - public var lastTexture: MTLTexture? - public override init(frame: CGRect) { super.init(frame: frame) } @@ -32,12 +32,12 @@ public final class MetalImageView: UIView, RemoteImageViewContentProtocol { public override func layoutSubviews() { super.layoutSubviews() metalView.frame = bounds + image = redrawingImage(uiImage, bounds: bounds, imageRedrawingStyle: imageRedrawingStyle) } public func setImage(_ image: UIImage?, animated: Bool?) { - self.image = redrawingImage(image, imageRedrawingStyle: imageRedrawingStyle) uiImage = image - metalView.setNeedsDisplay() + setNeedsLayout() if let appearanceAnimation = appearanceAnimation, animated == true { self.alpha = appearanceAnimation.startAlpha UIView.animate( @@ -60,8 +60,8 @@ public final class MetalImageView: UIView, RemoteImageViewContentProtocol { public var imageRedrawingStyle: ImageRedrawingStyle? { didSet { - if oldValue != imageRedrawingStyle, let uiImage { - image = redrawingImage(uiImage, imageRedrawingStyle: imageRedrawingStyle) + if oldValue != imageRedrawingStyle { + setNeedsLayout() } } } @@ -75,56 +75,74 @@ public final class MetalImageView: UIView, RemoteImageViewContentProtocol { } } -extension UIImage.Orientation { - fileprivate var layerTransform: CGAffineTransform { - switch self { - case .up: - return .identity - case .upMirrored: - return CGAffineTransform(scaleX: -1, y: 1) - case .down: - return CGAffineTransform(rotationAngle: .pi) - case .downMirrored: - return CGAffineTransform(rotationAngle: .pi).scaledBy(x: -1, y: 1) - case .left: - return CGAffineTransform(rotationAngle: -.pi / 2) - case .leftMirrored: - return CGAffineTransform(rotationAngle: -.pi / 2).scaledBy(x: -1, y: 1) - case .right: - return CGAffineTransform(rotationAngle: .pi / 2) - case .rightMirrored: - return CGAffineTransform(rotationAngle: .pi / 2).scaledBy(x: -1, y: 1) - @unknown default: - return .identity - } - } -} - -extension CALayer { - fileprivate func setContents(_ source: Image?) { - contents = source?.cgImage - contentsScale = source?.scale ?? 1 - setAffineTransform(source?.imageOrientation.layerTransform ?? .identity) - } -} - fileprivate func redrawingImage( _ image: UIImage?, + bounds: CGRect, imageRedrawingStyle: ImageRedrawingStyle? ) -> CIImage? { guard let image, let cgImage = image.cgImage else { return nil } - let baseImage = CIImage(cgImage: cgImage) + let ciImage = CIImage(cgImage: cgImage) + let extent = ciImage.extent - guard let tintColor = imageRedrawingStyle?.tintColor.ciColor, - let coloredImage = ImageGeneratorType.constantColor(color: tintColor).imageGenerator(), - let ciImage = ImageComposerType.sourceAtop.imageComposer(baseImage)(coloredImage) else { - return baseImage + let identityFilter: ImageFilter = { $0 } + + let tintModeFilter: ImageFilter + + if let tintColor = imageRedrawingStyle?.tintColor, + let coloredImage = ImageGeneratorType.constantColor(color: tintColor.ciColor) + .imageGenerator() { + let mode = imageRedrawingStyle?.tintMode ?? .sourceAtop + tintModeFilter = { mode.composerType.imageComposer($0)(coloredImage) } + } else { + tintModeFilter = identityFilter } - return ciImage.cropped(to: baseImage.extent) + let filter = imageRedrawingStyle?.effects.compactMap { + switch $0 { + case let .blur(radius: radius): + let scaleX = bounds.width / extent.width + let scaleY = bounds.height / extent.height + let scale = max(scaleX, scaleY) * 2 + return ImageBlurType.gaussian(radius: radius / scale).imageFilter + case let .tint(color: color, mode: mode): + guard let mode, + let coloredImage = ImageGeneratorType.constantColor(color: color.ciColor) + .imageGenerator() + else { return nil } + return { mode.composerType.imageComposer($0)(coloredImage) } + } + }.reduce(identityFilter, combine) ?? identityFilter + + let contentRect = CIVector( + x: 0, + y: 0, + z: extent.width, + w: extent.height + ) + + return combine(tintModeFilter, filter, ImageCropType.crop(rect: contentRect).imageFilter)(ciImage) +} + +extension TintMode { + fileprivate var composerType: ImageComposerType { + switch self { + case .sourceIn: + return .sourceIn + case .sourceAtop: + return .sourceAtop + case .darken: + return .darken + case .lighten: + return .lighten + case .multiply: + return .multiply + case .screen: + return .screen + } + } } extension MetalImageView: MTKViewDelegate { @@ -139,26 +157,18 @@ extension MetalImageView: MTKViewDelegate { let buffer = commandQueue.makeCommandBuffer() let drawableSize = view.drawableSize - let boundsSize: CGSize - if imageContentMode.scale == .noScale { - boundsSize = drawableSize - } else { - boundsSize = view.bounds.size - } - let layout = layout( contentMode: imageContentMode, contentSize: image.extent.size, - boundsSize: boundsSize + boundsSize: view.bounds.size ) - let ciContext = CIContext() let colorSpace = CGColorSpaceCreateDeviceRGB() let bounds = CGRect(origin: CGPoint.zero, size: drawableSize) - let screenFactorX = drawableSize.width / boundsSize.width - let screenFactorY = drawableSize.height / boundsSize.height + let screenFactorX = drawableSize.width / view.bounds.width + let screenFactorY = drawableSize.height / view.bounds.height let scaleX = layout.width / image.extent.width * screenFactorX let scaleY = layout.height / image.extent.height * screenFactorY @@ -174,7 +184,15 @@ extension MetalImageView: MTKViewDelegate { y: layout.origin.y * screenFactorY )) - ciContext.render( + let ciContext: CIContext? + + #if DEBUG + ciContext = isSnapshotTest ? CIContext() : self.ciContext + #else + ciContext = self.ciContext + #endif + + ciContext?.render( scaledImage, to: drawable.texture, commandBuffer: buffer, @@ -242,3 +260,8 @@ private func makeOrigin( } return CGPoint(x: x, y: y) } + +#if DEBUG +private let isSnapshotTest = ProcessInfo.processInfo + .environment["XCTestConfigurationFilePath"] != nil +#endif diff --git a/client/ios/Core/CommonCore/RemoteImageView.swift b/client/ios/Core/CommonCore/RemoteImageView.swift index dcdbe7fc1..88b2313de 100644 --- a/client/ios/Core/CommonCore/RemoteImageView.swift +++ b/client/ios/Core/CommonCore/RemoteImageView.swift @@ -1,3 +1,5 @@ +// Copyright 2022 Yandex LLC. All rights reserved. + import UIKit import Base @@ -43,6 +45,7 @@ public final class RemoteImageView: UIView, RemoteImageViewContentProtocol { } } setNeedsLayout() + layoutIfNeeded() } public func setImage(_ image: UIImage?, animated: Bool?) { @@ -60,6 +63,8 @@ public final class RemoteImageView: UIView, RemoteImageViewContentProtocol { } else { updateContent() } + setNeedsLayout() + layoutIfNeeded() } private var image: UIImage? diff --git a/client/ios/Core/CommonCore/RemoteImageViewContainer.swift b/client/ios/Core/CommonCore/RemoteImageViewContainer.swift index 746593291..9750b7200 100644 --- a/client/ios/Core/CommonCore/RemoteImageViewContainer.swift +++ b/client/ios/Core/CommonCore/RemoteImageViewContainer.swift @@ -1,4 +1,4 @@ -// Copyright 2015 Yandex LLC. All rights reserved. +// Copyright 2022 Yandex LLC. All rights reserved. import UIKit diff --git a/client/ios/Core/CommonCore/RemoteImageViewContentProtocol.swift b/client/ios/Core/CommonCore/RemoteImageViewContentProtocol.swift index fbd74aec1..7e2fa76d9 100644 --- a/client/ios/Core/CommonCore/RemoteImageViewContentProtocol.swift +++ b/client/ios/Core/CommonCore/RemoteImageViewContentProtocol.swift @@ -4,5 +4,5 @@ import Foundation import UIKit public protocol RemoteImageViewContentProtocol: UIView, ImageViewProtocol { - func setImage(_ image: UIImage?, animated: Bool?) + func setImage(_ image: UIImage?, animated: Bool?) } diff --git a/client/ios/Core/Networking/HTTPHeaders.swift b/client/ios/Core/Networking/HTTPHeaders.swift index e8b4c70e6..aeebd76ec 100644 --- a/client/ios/Core/Networking/HTTPHeaders.swift +++ b/client/ios/Core/Networking/HTTPHeaders.swift @@ -67,3 +67,47 @@ extension HTTPHeaders { extension HTTPHeaders { public static let yandexUID = "X-Yandex-RandomUID" } + +extension HTTPHeaders { + /** + Creates a new instance, combining current headers with newHeaders. + Conflicting keys get a value from newHeaders. + "Cookies" field is merged separately, all cookies are stored, + conflicting cookie keys get a value from newHeaders' cookies. + **/ + public func merged(with newHeaders: HTTPHeaders) -> HTTPHeaders { + let cookie = mergeCookies(self["Cookie"], newHeaders["Cookie"]) + return self + newHeaders + + (cookie != "" ? HTTPHeaders(headersDictionary: ["Cookie": cookie]) : HTTPHeaders.empty) + } + + private func mergeCookies(_ cookies: String?, _ newCookies: String?) -> String { + var result = parseCookies(cookies ?? "") + parseCookies(newCookies ?? "").forEach { newCookie in + if let index = result.firstIndex(where: { $0.name == newCookie.name }) { + result[index] = newCookie + } else { + result.append(newCookie) + } + } + return result.map { "\($0.name)=\($0.value)" }.joined(separator: "; ") + } + + private func parseCookies(_ cookies: String) -> [(name: String, value: String)] { + cookies + .components(separatedBy: "; ") + .filter { $0 != "" } + .map { + let keyVal = $0.components(separatedByFirst: "=") + return (name: keyVal[0], value: keyVal.count > 1 ? keyVal[1] : "") + } + } +} + +extension String { + fileprivate func components(separatedByFirst delimiter: String) -> [String] { + let comps = components(separatedBy: delimiter) + guard comps.count > 1 else { return comps } + return [comps[0], comps.suffix(from: 1).joined(separator: delimiter)] + } +} diff --git a/client/ios/Core/Networking/RemoteImageHolder.swift b/client/ios/Core/Networking/RemoteImageHolder.swift index eebbe8b7c..9977042a8 100644 --- a/client/ios/Core/Networking/RemoteImageHolder.swift +++ b/client/ios/Core/Networking/RemoteImageHolder.swift @@ -118,6 +118,6 @@ extension RemoteImageHolder: CustomDebugStringConvertible { extension RemoteImageHolder { public func reused(with placeholder: ImagePlaceholder?, remoteImageURL: URL?) -> ImageHolder? { - (placeholder === placeholder && url == remoteImageURL) ? self : nil + (self.placeholder === placeholder && url == remoteImageURL) ? self : nil } } diff --git a/client/ios/Core/copy.sh b/client/ios/Core/copy.sh index c255c0836..fb6cd7796 100755 --- a/client/ios/Core/copy.sh +++ b/client/ios/Core/copy.sh @@ -53,6 +53,14 @@ cp $1/src/base/ios/yandex/storage/KeyValueDirectStoringSupporting.swift Base cp $1/src/base/ios/yandex/storage/KeyValueStorage.swift Base cp $1/src/base/ios/yandex/storage/SettingProperty.swift Base cp $1/src/base/ios/yandex/storage/SingleValueCoder.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/FilterProtocol.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageComposer.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageFilters.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageGenerator.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/TintMode.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageBlur.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageCrop.swift Base +cp $1/src/base/ios/yandex/thick_ui/filters/ImageEffect.swift Base cp $1/src/base/ios/yandex/thick_ui/graphics/InternalImageDescriptor.swift Base cp $1/src/base/ios/yandex/thick_ui/types/color/ColorExtensions.swift Base cp $1/src/base/ios/yandex/thick_ui/types/color/RGBAColorExtensions.swift Base @@ -126,6 +134,11 @@ cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/ImageContaining.swift cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/ImageLayerLayout.swift CommonCore cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/ObjectsReusability.swift CommonCore cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/RemoteImageView.swift CommonCore +cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/RemoteImageViewContainer.swift CommonCore +cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/RemoteImageViewContentProtocol.swift CommonCore +cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/MetalImageView.swift CommonCore +cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/ImageViewProtocol.swift CommonCore +cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/ImageRedrawingStyle.swift CommonCore cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/Resetting.swift CommonCore cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/Theme.swift CommonCore cp $1/src/yandex/ios/search_app/CommonCore/CommonCore/Base/UIStyles.swift CommonCore diff --git a/client/ios/DivKit/Extensions/DivBackgroundExtensions.swift b/client/ios/DivKit/Extensions/DivBackgroundExtensions.swift index 4da540170..9437060e3 100644 --- a/client/ios/DivKit/Extensions/DivBackgroundExtensions.swift +++ b/client/ios/DivKit/Extensions/DivBackgroundExtensions.swift @@ -9,7 +9,8 @@ import Networking extension DivBackground { func makeBlockBackground( with imageHolderFactory: ImageHolderFactory, - expressionResolver: ExpressionResolver + expressionResolver: ExpressionResolver, + metalImageRenderingEnabled: Bool ) -> LayoutKit.Background? { switch self { case let .divLinearGradient(gradient): @@ -38,7 +39,9 @@ extension DivBackground { imageBackground.resolveImageUrl(expressionResolver) ), contentMode: imageBackground.resolveContentMode(expressionResolver), - alpha: imageBackground.resolveAlpha(expressionResolver) + alpha: imageBackground.resolveAlpha(expressionResolver), + effects: imageBackground.makeEffects(with: expressionResolver), + metalImageRenderingEnabled: metalImageRenderingEnabled ) return .image(image) case let .divSolidBackground(solidBackground): @@ -56,3 +59,9 @@ extension DivBackground { } extension DivImageBackground: DivImageContentMode {} + +extension DivImageBackground { + fileprivate func makeEffects(with resolver: ExpressionResolver) -> [ImageEffect] { + filters?.compactMap { $0.makeImageEffect(with: resolver)} ?? [] + } +} diff --git a/client/ios/DivKit/Extensions/DivBase/DivBaseExtensions.swift b/client/ios/DivKit/Extensions/DivBase/DivBaseExtensions.swift index 3dad02ebe..87dc33f24 100644 --- a/client/ios/DivKit/Extensions/DivBase/DivBaseExtensions.swift +++ b/client/ios/DivKit/Extensions/DivBase/DivBaseExtensions.swift @@ -62,7 +62,8 @@ extension DivBase { background, to: block, imageHolderFactory: context.imageHolderFactory, - expressionResolver: expressionResolver + expressionResolver: expressionResolver, + metalImageRenderingEnabled: context.flagsInfo.metalImageRenderingEnabled ) .addingDecorations( boundary: border.makeBoundaryTrait(with: expressionResolver), @@ -222,7 +223,8 @@ extension DivBase { _ backgrounds: [DivBackground]?, to block: Block, imageHolderFactory: ImageHolderFactory, - expressionResolver: ExpressionResolver + expressionResolver: ExpressionResolver, + metalImageRenderingEnabled: Bool ) -> Block { guard let backgrounds = backgrounds else { return block @@ -232,7 +234,8 @@ extension DivBase { if backgrounds.count == 1 { guard let background = backgrounds[0].makeBlockBackground( with: imageHolderFactory, - expressionResolver: expressionResolver + expressionResolver: expressionResolver, + metalImageRenderingEnabled: metalImageRenderingEnabled ) else { return block } @@ -245,7 +248,8 @@ extension DivBase { let blockBackgrounds = backgrounds.compactMap { $0.makeBlockBackground( with: imageHolderFactory, - expressionResolver: expressionResolver + expressionResolver: expressionResolver, + metalImageRenderingEnabled: metalImageRenderingEnabled ) } guard let background = blockBackgrounds.composite() else { diff --git a/client/ios/DivKit/Extensions/DivFilterExtensions.swift b/client/ios/DivKit/Extensions/DivFilterExtensions.swift new file mode 100644 index 000000000..b75906352 --- /dev/null +++ b/client/ios/DivKit/Extensions/DivFilterExtensions.swift @@ -0,0 +1,11 @@ +import Base +import CoreGraphics + +extension DivFilter { + func makeImageEffect(with resolver: ExpressionResolver) -> ImageEffect { + switch self { + case .divBlur(let blur): + return .blur(radius: CGFloat(blur.resolveRadius(resolver) ?? 0)) + } + } +} diff --git a/client/ios/DivKit/Extensions/DivImage/DivImageExtensions.swift b/client/ios/DivKit/Extensions/DivImage/DivImageExtensions.swift index 3439407a9..27f621a40 100644 --- a/client/ios/DivKit/Extensions/DivImage/DivImageExtensions.swift +++ b/client/ios/DivKit/Extensions/DivImage/DivImageExtensions.swift @@ -1,5 +1,6 @@ import Foundation +import BaseUI import CommonCore import Networking import LayoutKit @@ -39,8 +40,35 @@ extension DivImage: DivBlockModeling, DivImageProtocol { height: resolveHeight(context), contentMode: resolveContentMode(expressionResolver), tintColor: resolveTintColor(expressionResolver), + tintMode: resolveTintMode(expressionResolver).tintMode, + effects: makeEffects(with: expressionResolver), metalImageRenderingEnabled: context.flagsInfo.metalImageRenderingEnabled, appearanceAnimation: appearanceAnimation?.makeAppearanceAnimation(with: expressionResolver) ) } } + +extension DivBlendMode { + fileprivate var tintMode: TintMode { + switch self { + case .sourceIn: + return .sourceIn + case .sourceAtop: + return .sourceAtop + case .darken: + return .darken + case .lighten: + return .lighten + case .multiply: + return .multiply + case .screen: + return .screen + } + } +} + +extension DivImage { + fileprivate func makeEffects(with resolver: ExpressionResolver) -> [ImageEffect] { + filters?.compactMap { $0.makeImageEffect(with: resolver)} ?? [] + } +} diff --git a/client/ios/DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift b/client/ios/DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift index f00bb6ba8..c3a1d57b8 100644 --- a/client/ios/DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift +++ b/client/ios/DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift @@ -31,7 +31,9 @@ public final class ImageExtensionHandler: DivExtensionHandler { widthTrait: block.widthTrait, height: block.height, contentMode: block.contentMode, - tintColor: block.tintColor + tintColor: block.tintColor, + tintMode: block.tintMode, + effects: block.effects ) } } diff --git a/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock+Copy.swift b/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock+Copy.swift index 110f3c87b..7a577921e 100644 --- a/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock+Copy.swift +++ b/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock+Copy.swift @@ -8,6 +8,8 @@ extension ImageBlock { height: height, contentMode: contentMode, tintColor: tintColor, + tintMode: tintMode, + effects: effects, metalImageRenderingEnabled: metalImageRenderingEnabled, accessibilityElement: accessibilityElement ) diff --git a/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock.swift b/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock.swift index 068939fdb..71c9d7134 100644 --- a/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock.swift +++ b/client/ios/LayoutKit/LayoutKit/Blocks/ImageBlock.swift @@ -10,6 +10,8 @@ public final class ImageBlock: ImageBaseBlock { public let height: ImageBlockHeight public let contentMode: ImageContentMode public let tintColor: Color? + public let tintMode: TintMode? + public let effects: [ImageEffect] public let accessibilityElement: AccessibilityElement? public let appearanceAnimation: TransitioningAnimation? public let metalImageRenderingEnabled: Bool @@ -20,6 +22,8 @@ public final class ImageBlock: ImageBaseBlock { height: ImageBlockHeight, contentMode: ImageContentMode, tintColor: Color?, + tintMode: TintMode?, + effects: [ImageEffect], metalImageRenderingEnabled: Bool = false, accessibilityElement: AccessibilityElement? = nil, appearanceAnimation: TransitioningAnimation? = nil @@ -29,6 +33,8 @@ public final class ImageBlock: ImageBaseBlock { self.height = height self.contentMode = contentMode self.tintColor = tintColor + self.tintMode = tintMode + self.effects = effects self.metalImageRenderingEnabled = metalImageRenderingEnabled self.accessibilityElement = accessibilityElement self.appearanceAnimation = appearanceAnimation @@ -40,6 +46,8 @@ public final class ImageBlock: ImageBaseBlock { heightTrait: LayoutTrait = .intrinsic, contentMode: ImageContentMode = .default, tintColor: Color? = nil, + tintMode: TintMode? = nil, + effects: [ImageEffect] = [], metalImageRenderingEnabled: Bool = false, accessibilityElement: AccessibilityElement? = nil, appearanceAnimation: TransitioningAnimation? = nil @@ -50,6 +58,8 @@ public final class ImageBlock: ImageBaseBlock { height: .trait(heightTrait), contentMode: contentMode, tintColor: tintColor, + tintMode: tintMode, + effects: effects, metalImageRenderingEnabled: metalImageRenderingEnabled, accessibilityElement: accessibilityElement, appearanceAnimation: appearanceAnimation @@ -61,6 +71,8 @@ public final class ImageBlock: ImageBaseBlock { size: CGSize, contentMode: ImageContentMode = .default, tintColor: Color? = nil, + tintMode: TintMode? = nil, + effects: [ImageEffect] = [], metalImageRenderingEnabled: Bool = false, accessibilityElement: AccessibilityElement? = nil, appearanceAnimation: TransitioningAnimation? = nil @@ -71,6 +83,8 @@ public final class ImageBlock: ImageBaseBlock { heightTrait: .fixed(size.height), contentMode: contentMode, tintColor: tintColor, + tintMode: tintMode, + effects: effects, metalImageRenderingEnabled: metalImageRenderingEnabled, accessibilityElement: accessibilityElement, appearanceAnimation: appearanceAnimation diff --git a/client/ios/LayoutKit/LayoutKit/UI/Blocks/ImageBlock+UIViewRenderableBlock.swift b/client/ios/LayoutKit/LayoutKit/UI/Blocks/ImageBlock+UIViewRenderableBlock.swift index ddf67e1e6..f1168abc3 100644 --- a/client/ios/LayoutKit/LayoutKit/UI/Blocks/ImageBlock+UIViewRenderableBlock.swift +++ b/client/ios/LayoutKit/LayoutKit/UI/Blocks/ImageBlock+UIViewRenderableBlock.swift @@ -21,9 +21,9 @@ extension ImageBlock { remoteImageViewContainer.imageHolder = imageHolder } remoteImageViewContainer.contentView.imageContentMode = contentMode - if let tintColor = tintColor { - remoteImageViewContainer.contentView.imageRedrawingStyle = .init(tintColor: tintColor) - } + remoteImageViewContainer.contentView.imageRedrawingStyle = ImageRedrawingStyle(tintColor: tintColor, + tintMode: tintMode, + effects: effects) remoteImageViewContainer.contentView.isUserInteractionEnabled = false remoteImageViewContainer.isUserInteractionEnabled = false remoteImageViewContainer.applyAccessibility(accessibilityElement) diff --git a/client/ios/LayoutKit/LayoutKit/UI/Views/Background+UIViewRendering.swift b/client/ios/LayoutKit/LayoutKit/UI/Views/Background+UIViewRendering.swift index 445a0790b..70aafda0f 100644 --- a/client/ios/LayoutKit/LayoutKit/UI/Views/Background+UIViewRendering.swift +++ b/client/ios/LayoutKit/LayoutKit/UI/Views/Background+UIViewRendering.swift @@ -88,8 +88,12 @@ extension BackgroundView { case .solidColor, .tiledImage: view = ColoredView() - case .image: - view = RemoteImageViewContainer(contentView: RemoteImageView()) + case .image(let backgroundImage): + if backgroundImage.metalImageRenderingEnabled { + view = RemoteImageViewContainer(contentView: MetalImageView()) + } else { + view = RemoteImageViewContainer(contentView: RemoteImageView()) + } case .ninePatchImage: view = RemoteImageViewContainer(contentView: NinePatchImageView()) @@ -148,6 +152,7 @@ extension BackgroundView { imageViewContainer.contentView.clipsToBounds = true imageViewContainer.contentView.alpha = CGFloat(image.alpha) imageViewContainer.contentView.imageContentMode = image.contentMode + imageViewContainer.contentView.imageRedrawingStyle = ImageRedrawingStyle(tintColor: nil, effects: image.effects) if imageViewContainer.imageHolder != image.imageHolder { imageViewContainer.imageHolder = image.imageHolder } diff --git a/client/ios/LayoutKit/LayoutKit/ViewModels/BackgroundImage.swift b/client/ios/LayoutKit/LayoutKit/ViewModels/BackgroundImage.swift index 8e3e65c7d..bf78b03a3 100644 --- a/client/ios/LayoutKit/LayoutKit/ViewModels/BackgroundImage.swift +++ b/client/ios/LayoutKit/LayoutKit/ViewModels/BackgroundImage.swift @@ -1,18 +1,25 @@ +import BaseUI import CommonCore public struct BackgroundImage { let imageHolder: ImageHolder let contentMode: ImageContentMode let alpha: Double + let effects: [ImageEffect] + let metalImageRenderingEnabled: Bool public init( imageHolder: ImageHolder, contentMode: ImageContentMode = .default, - alpha: Double = 1.0 + alpha: Double = 1.0, + effects: [ImageEffect], + metalImageRenderingEnabled: Bool ) { self.imageHolder = imageHolder self.contentMode = contentMode self.alpha = alpha + self.effects = effects + self.metalImageRenderingEnabled = metalImageRenderingEnabled } } @@ -20,7 +27,8 @@ extension BackgroundImage: Equatable { public static func ==(lhs: BackgroundImage, rhs: BackgroundImage) -> Bool { lhs.imageHolder == rhs.imageHolder && lhs.contentMode == rhs.contentMode && - lhs.alpha == rhs.alpha + lhs.alpha == rhs.alpha && + lhs.effects == rhs.effects } } diff --git a/client/ios/Tests/reference_snapshots/div-image/blur_320@2x.png b/client/ios/Tests/reference_snapshots/div-image/blur_320@2x.png index d74f033d1..f941979b5 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/blur_320@2x.png and b/client/ios/Tests/reference_snapshots/div-image/blur_320@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/blur_375@2x.png b/client/ios/Tests/reference_snapshots/div-image/blur_375@2x.png index adb4879f6..f95a99dd9 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/blur_375@2x.png and b/client/ios/Tests/reference_snapshots/div-image/blur_375@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/blur_375@3x.png b/client/ios/Tests/reference_snapshots/div-image/blur_375@3x.png index d0fd3f409..3d2225fbe 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/blur_375@3x.png and b/client/ios/Tests/reference_snapshots/div-image/blur_375@3x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/blur_414@2x.png b/client/ios/Tests/reference_snapshots/div-image/blur_414@2x.png index 8fe62865d..1f428589f 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/blur_414@2x.png and b/client/ios/Tests/reference_snapshots/div-image/blur_414@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/blur_414@3x.png b/client/ios/Tests/reference_snapshots/div-image/blur_414@3x.png index 275a1ca67..e73d6021f 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/blur_414@3x.png and b/client/ios/Tests/reference_snapshots/div-image/blur_414@3x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_320@2x.png b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_320@2x.png index 1a342cd64..d6097caa5 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_320@2x.png and b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_320@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@2x.png b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@2x.png index 1a342cd64..d6097caa5 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@2x.png and b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@3x.png b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@3x.png index fb605c791..c077ec670 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@3x.png and b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_375@3x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@2x.png b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@2x.png index 1a342cd64..d6097caa5 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@2x.png and b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@3x.png b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@3x.png index fb605c791..c077ec670 100644 Binary files a/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@3x.png and b/client/ios/Tests/reference_snapshots/div-image/no_scale_top_left_414@3x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-text/blur-background_320@2x.png b/client/ios/Tests/reference_snapshots/div-text/blur-background_320@2x.png index 6318eacec..f270df8cd 100644 Binary files a/client/ios/Tests/reference_snapshots/div-text/blur-background_320@2x.png and b/client/ios/Tests/reference_snapshots/div-text/blur-background_320@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-text/blur-background_375@2x.png b/client/ios/Tests/reference_snapshots/div-text/blur-background_375@2x.png index 6318eacec..f270df8cd 100644 Binary files a/client/ios/Tests/reference_snapshots/div-text/blur-background_375@2x.png and b/client/ios/Tests/reference_snapshots/div-text/blur-background_375@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-text/blur-background_375@3x.png b/client/ios/Tests/reference_snapshots/div-text/blur-background_375@3x.png index fc3a69c93..85c1a4c23 100644 Binary files a/client/ios/Tests/reference_snapshots/div-text/blur-background_375@3x.png and b/client/ios/Tests/reference_snapshots/div-text/blur-background_375@3x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-text/blur-background_414@2x.png b/client/ios/Tests/reference_snapshots/div-text/blur-background_414@2x.png index 6318eacec..f270df8cd 100644 Binary files a/client/ios/Tests/reference_snapshots/div-text/blur-background_414@2x.png and b/client/ios/Tests/reference_snapshots/div-text/blur-background_414@2x.png differ diff --git a/client/ios/Tests/reference_snapshots/div-text/blur-background_414@3x.png b/client/ios/Tests/reference_snapshots/div-text/blur-background_414@3x.png index fc3a69c93..85c1a4c23 100644 Binary files a/client/ios/Tests/reference_snapshots/div-text/blur-background_414@3x.png and b/client/ios/Tests/reference_snapshots/div-text/blur-background_414@3x.png differ