Caps-iOS/CapCollector/Data/Cap.swift

277 lines
7.2 KiB
Swift
Raw Normal View History

2019-03-15 13:19:19 +01:00
//
// Cap.swift
// CapCollector
//
// Created by Christoph on 19.11.18.
// Copyright © 2018 CH. All rights reserved.
//
import Foundation
import UIKit
import CoreImage
2020-05-16 11:21:55 +02:00
import SQLite
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
struct Cap {
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
// MARK: - Static variables
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let sufficientImageCount = 10
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let imageWidth = 299 // New for XCode models, 227/229 for turicreate
static let imageSize = CGSize(width: imageWidth, height: imageWidth)
2019-03-15 13:19:19 +01:00
static let jpgQuality: CGFloat = 0.3
private static let mosaicColumns = 40
static let mosaicCellSize: CGFloat = 60
private static let mosaicRowHeight = mosaicCellSize * 0.866
private static let mosaicMargin = mosaicCellSize - mosaicRowHeight
// MARK: - Variables
/// The unique number of the cap
let id: Int
/// The tile position of the cap
2020-05-16 11:21:55 +02:00
let tile: Int
2019-03-15 13:19:19 +01:00
/// The name of the cap
2020-05-16 11:21:55 +02:00
let name: String
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
/// The name of the cap without special characters
let cleanName: String
2019-03-15 13:19:19 +01:00
/// The number of images existing for the cap
2020-05-16 11:21:55 +02:00
let count: Int
2019-03-15 13:19:19 +01:00
/// The average color of the cap
2020-05-16 11:21:55 +02:00
let color: UIColor
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
/// Indicate if the cap can be found by the recognition model
let matched: Bool
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
/// Indicate if the cap is present on the server
let uploaded: Bool
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
// MARK: Init
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
init(name: String, id: Int, color: UIColor) {
self.id = id
self.count = 1
self.name = name
self.cleanName = ""
self.tile = id
self.color = color
self.matched = false
self.uploaded = false
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
// MARK: SQLite
2020-05-16 11:21:55 +02:00
static let table = Table("data")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let createQuery: String = {
table.create(ifNotExists: true) { t in
t.column(rowId, primaryKey: true)
t.column(rowName)
t.column(rowCount)
t.column(rowTile)
t.column(rowRed)
t.column(rowGreen)
t.column(rowBlue)
t.column(rowMatched)
t.column(rowUploaded)
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
}()
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowId = Expression<Int>("id")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowName = Expression<String>("name")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowCount = Expression<Int>("count")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowTile = Expression<Int>("tile")
2020-05-16 11:21:55 +02:00
static let rowRed = Expression<Int>("red")
static let rowGreen = Expression<Int>("green")
static let rowBlue = Expression<Int>("blue")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowMatched = Expression<Bool>("matched")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
static let rowUploaded = Expression<Bool>("uploaded")
2019-03-15 13:19:19 +01:00
2020-05-16 11:21:55 +02:00
init(row: Row) {
self.id = row[Cap.rowId]
self.name = row[Cap.rowName]
self.count = row[Cap.rowCount]
self.tile = row[Cap.rowTile]
self.cleanName = name.clean
self.matched = row[Cap.rowMatched]
self.uploaded = row[Cap.rowUploaded]
let r = CGFloat(row[Cap.rowRed]) / 255
let g = CGFloat(row[Cap.rowGreen]) / 255
let b = CGFloat(row[Cap.rowBlue]) / 255
self.color = UIColor(red: r, green: g, blue: b, alpha: 1.0)
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
init(id: Int, name: String, count: Int) {
self.id = id
self.name = name
self.count = count
self.tile = id - 1
self.cleanName = name.clean
self.matched = false
self.color = UIColor.gray
self.uploaded = false
}
2020-05-16 11:21:55 +02:00
var insertQuery: Insert {
let colors = color.rgb
return Cap.table.insert(
Cap.rowId <- id,
Cap.rowName <- name,
Cap.rowCount <- count,
Cap.rowTile <- tile,
Cap.rowRed <- colors.red,
Cap.rowGreen <- colors.green,
Cap.rowBlue <- colors.blue,
Cap.rowMatched <- matched,
Cap.rowUploaded <- uploaded)
}
2020-05-16 11:21:55 +02:00
// MARK: Text
2020-05-16 11:21:55 +02:00
func matchDescription(match: Float?) -> String {
guard let match = match else {
return hasSufficientImages ? "" : "⚠️"
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
let percent = Int((match * 100).rounded())
return String(format: "%d %%", arguments: [percent])
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
/// The cap id and the number of images
var subtitle: String {
guard count != 1 else {
return "\(id) (1 image)"
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
return "\(id) (\(count) images)"
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
// MARK: - Images
var hasSufficientImages: Bool {
count > Cap.sufficientImageCount
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
var hasImage: Bool {
app.storage.hasImage(for: id)
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
/// The main image of the cap
var image: UIImage? {
app.storage.image(for: id)
}
/// The main image of the cap
var thumbnail: UIImage? {
app.storage.thumbnail(for: id)
}
2020-05-16 11:21:55 +02:00
static func thumbnail(for image: UIImage) -> UIImage {
let len = GridViewController.len * 2
return image.resize(to: CGSize.init(width: len, height: len))
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
func updateLocalThumbnail() {
guard let img = image else {
2019-03-15 13:19:19 +01:00
return
}
2020-05-16 11:21:55 +02:00
let thumbnail = Cap.thumbnail(for: img)
guard app.storage.save(thumbnail: thumbnail, for: id) else {
error("Failed to save thumbnail")
2019-03-15 13:19:19 +01:00
return
}
2020-05-16 11:21:55 +02:00
log("Created thumbnail for cap \(id)")
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
func updateLocalColor() {
guard let color = image?.averageColor else {
return
}
2020-05-16 11:21:55 +02:00
app.database.update(color: color, for: id)
2019-03-15 13:19:19 +01:00
}
/**
2020-05-16 11:21:55 +02:00
Download the main image of the cap.
- Note: The downloaded image is automatically saved to disk
- returns: `true`, if the image will be downloaded, `false`, if the image is already being downloaded.
2019-03-15 13:19:19 +01:00
*/
2020-05-16 11:21:55 +02:00
@discardableResult
func downloadMainImage(completion: @escaping (_ image: UIImage?) -> Void) -> Bool {
app.database.downloadMainImage(for: id, completion: completion)
2019-03-15 13:19:19 +01:00
}
2020-05-16 11:21:55 +02:00
/**
Download a specified image of the cap.
- parameter number: The number of the image
- parameter completion: The completion handler, called with the image if successful
- parameter image: The image, if the download was successful, or nil on error
- returns: `true`, if the image will be downloaded, `false`, if the image is already being downloaded.
*/
func downloadImage(_ number: Int, completion: @escaping (_ image: UIImage?) -> Void) -> Bool {
app.database.downloadImage(for: id, version: number, completion: completion)
2019-03-15 13:19:19 +01:00
}
2019-03-15 13:19:19 +01:00
}
// MARK: - Protocol Hashable
extension Cap: Hashable {
static func == (lhs: Cap, rhs: Cap) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - Protocol CustomStringConvertible
extension Cap: CustomStringConvertible {
var description: String {
2020-05-16 11:21:55 +02:00
let rgb = color.rgb
return String(format: "%04d", id) + ";\(name);\(count);\(tile);\(rgb.red);\(rgb.green);\(rgb.blue)\n"
2019-03-15 13:19:19 +01:00
}
}
// MARK: - Protocol Logger
2020-05-16 11:21:55 +02:00
extension Cap: Logger { }
2019-03-15 13:19:19 +01:00
// MARK: - String extension
extension String {
var clean: String {
return lowercased().replacingOccurrences(of: "[^a-z0-9 ]", with: "", options: .regularExpression)
}
}
// MARK: - Int extension
private extension Int {
var isEven: Bool {
return self % 2 == 0
}
}