ios/SpectrumKitSample/SpectrumKitSample-iOS/SpectrumViewModel.swift (163 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. import Photos import UIKit.UIImage protocol SpectrumViewModelDelegate: class { func viewModelDidUpdate(_ viewModel: SpectrumViewModel) func viewModel(_ viewModel: SpectrumViewModel, didFailTranscodingWith error: Swift.Error) func viewModelDidFinishTranscoding(_ viewModel: SpectrumViewModel) } final class SpectrumViewModel: NSObject { // MARK: - Public Properties weak var delegate: SpectrumViewModelDelegate? private(set) var source = ImageSource(initialImage: ()) { didSet { guard self.source != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var sourceImageDescription: String? { return "Size: \(self.source.image.size) @\(self.source.image.scale)x" } var inputType: IOType = .file { didSet { guard self.inputType != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var outputType: IOType = .file { didSet { guard self.outputType != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var outputFormat: OutputImageFormat = .jpeg { didSet { guard self.outputFormat != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var compressionMode: CompressionMode = .any { didSet { guard self.compressionMode != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var actionButtonTitle: String { switch (self.inputType, self.outputType) { case (.file, .file): return NSLocalizedString("Transcode", comment: "Launch operation call to action button title") case (.file, .bitmap): return NSLocalizedString("Decode", comment: "Launch operation call to action button title") case (.bitmap, .file): return NSLocalizedString("Encode", comment: "Launch operation call to action button title") case (.bitmap, .bitmap): return NSLocalizedString("Transform", comment: "Launch operation call to action button title") } } var showsQuality: Bool { return self.outputType == .file && self.compressionMode != .lossless } var showsEncodingMode: Bool { return self.outputType == .file } var showsOutputFormat: Bool { return self.outputType == .file } var mustTranscode: Bool = true { didSet { guard self.mustTranscode != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var quality: Int = 80 { didSet { guard self.quality != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } static let qualityPickerOptions = (EncodeRequirementQualityMin ... EncodeRequirementQualityMax / 5).map { $0 * 5 } var rotation: ImageRotationOptions = .none { didSet { guard self.rotation != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var scaling: ImageScalingOptions = .none { didSet { guard self.scaling != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var cropping: ImageCroppingOptions = .none { didSet { guard self.cropping != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } private(set) var isTranscoding = false { didSet { guard self.isTranscoding != oldValue else { return } self.delegate?.viewModelDidUpdate(self) } } var isTranscodeButtonEnabled: Bool { return self.isTranscoding == false } let configuration = Configuration() // MARK: - Private Properties private var transcodedImageRepresentation: ImageRepresentation? // MARK: - Public Methods func load(sourceImage: ImageSource) { self.transcodedImageRepresentation = nil self.source = sourceImage } func transcode() { self.isTranscoding = true SpectrumController.transcode(options: self.makeOptions(), source: self.source, inputType: self.inputType, outputType: self.outputType, completion: { [weak self] result in guard let strongSelf = self else { return } strongSelf.isTranscoding = false switch result { case let .success(imageRepresentation): strongSelf.transcodedImageRepresentation = imageRepresentation strongSelf.delegate?.viewModelDidFinishTranscoding(strongSelf) case let .error(error): strongSelf.delegate?.viewModel(strongSelf, didFailTranscodingWith: error) } }) } func makeConfigurationViewModel() -> ConfigurationViewModel { return ConfigurationViewModel(configuration: self.configuration) } func makeImageDifferenceViewModel() throws -> ImageDifferenceViewModel { switch self.transcodedImageRepresentation { case let .bytes(data)?: return try ImageDifferenceViewModel(sourceImage: self.source.image, transcodedImageData: data, imageFormat: self.outputFormat) case .bitmap(_)?, nil: throw Error.unsupportedBitmapOutput } } // MARK: - Private Methods private func makeOptions() -> SpectrumController.Options { return SpectrumController.Options(encodeRequirement: self.makeEncodeRequirement(), rotateRequirement: self.makeRotateRequirement(), cropRequirement: self.cropping.requirement, resizeRequirement: self.scaling.requirement, outputImagePixelSpecification: self.makeOutputImagePixelSpecification(), configuration: self.configuration, metadata: nil) } private func makeRotateRequirement() -> RotateRequirement? { guard self.rotation.value != 0 else { return nil } return RotateRequirement(degrees: self.rotation.value) } private func makeEncodeRequirement() -> EncodeRequirement? { guard self.outputType != .bitmap else { return nil } return EncodeRequirement(format: self.outputFormat.imageFormat, mode: self.compressionMode.mode, quality: self.quality) } private func makeOutputImagePixelSpecification() -> ImagePixelSpecification? { return self.outputType == .bitmap ? .rgba : nil } }