Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalUI/ImageEditor/ImageEditorStrokeItem.swift
1 views
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit

class ImageEditorStrokeItem: ImageEditorItem {

    enum StrokeType: Equatable {
        case pen
        case highlighter
        case blur
    }

    // Until we need to serialize these items,
    // just use UIColor.
    let color: UIColor?

    let strokeType: StrokeType

    typealias StrokeSample = ImageEditorSample

    let unitSamples: [StrokeSample]

    // Expressed as a "Unit" value as a fraction of
    // min(width, height) of the destination viewport.
    let unitStrokeWidth: CGFloat

    func strokeWidth(forDstSize dstSize: CGSize) -> CGFloat {
        ImageEditorStrokeItem.strokeWidth(forUnitStrokeWidth: unitStrokeWidth, dstSize: dstSize)
    }

    init(
        color: UIColor? = nil,
        strokeType: StrokeType,
        unitSamples: [StrokeSample],
        unitStrokeWidth: CGFloat,
    ) {
        self.color = color
        self.strokeType = strokeType
        self.unitSamples = unitSamples
        self.unitStrokeWidth = unitStrokeWidth

        super.init(itemType: .stroke)
    }

    init(
        itemId: String,
        color: UIColor? = nil,
        strokeType: StrokeType,
        unitSamples: [StrokeSample],
        unitStrokeWidth: CGFloat,
    ) {
        self.color = color
        self.strokeType = strokeType
        self.unitSamples = unitSamples
        self.unitStrokeWidth = unitStrokeWidth

        super.init(itemId: itemId, itemType: .stroke)
    }

    // First value is default unitStrokeWidth for a given stroke type.
    // Second value is the power to raise adjustment factor (slider value) if the factor is greater than 1.
    private class func metrics(forStrokeType strokeType: StrokeType) -> (CGFloat, CGFloat) {
        switch strokeType {
        case .pen:
            return (0.02, 3)
        case .highlighter:
            return (0.04, 3)
        case .blur:
            return (0.05, 2)
        }
    }

    class func unitStrokeWidth(
        forStrokeType strokeType: StrokeType,
        widthAdjustmentFactor adjustmentFactor: CGFloat,
    ) -> CGFloat {
        let (defaultWidth, power) = metrics(forStrokeType: strokeType)
        let multiplier: CGFloat
        if adjustmentFactor > 1 {
            multiplier = pow(adjustmentFactor, power)
        } else {
            multiplier = adjustmentFactor
        }
        return defaultWidth * multiplier
    }

    class func strokeWidth(forUnitStrokeWidth unitStrokeWidth: CGFloat, dstSize: CGSize) -> CGFloat {
        return CGFloat.clamp01(unitStrokeWidth) * min(dstSize.width, dstSize.height)
    }
}