Signal-iOS/SignalServiceKit/Util/ImageMetadata/ImageMetadata.swift
2025-10-20 20:14:18 -05:00

114 lines
3.0 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public enum ImageFormat: CustomStringConvertible {
case png
case gif
case tiff
case jpeg
case bmp
case webp
case heic
case heif
public var description: String {
switch self {
case .png:
"ImageFormat_Png"
case .gif:
"ImageFormat_Gif"
case .tiff:
"ImageFormat_Tiff"
case .jpeg:
"ImageFormat_Jpeg"
case .bmp:
"ImageFormat_Bmp"
case .webp:
"ImageFormat_Webp"
case .heic:
"ImageFormat_Heic"
case .heif:
"ImageFormat_Heif"
}
}
public var mimeType: MimeType {
return self.mimeTypes.preferredMimeType
}
private var mimeTypes: (preferredMimeType: MimeType, alternativeMimeTypes: [MimeType]) {
switch self {
case .png: (.imagePng, [.imageApng, .imageVndMozillaApng])
case .gif: (.imageGif, [])
case .tiff: (.imageTiff, [.imageXTiff])
case .jpeg: (.imageJpeg, [])
case .bmp: (.imageBmp, [.imageXWindowsBmp])
case .webp: (.imageWebp, [])
case .heic: (.imageHeic, [])
case .heif: (.imageHeif, [])
}
}
public var fileExtension: String {
// All known ImageFormats must have a corresponding extension.
return MimeTypeUtil.fileExtensionForMimeType(mimeType.rawValue)!
}
internal func isValid(mimeType: String) -> Bool {
owsAssertDebug(!mimeType.isEmpty)
let mimeTypes = self.mimeTypes
return ([mimeTypes.preferredMimeType] + mimeTypes.alternativeMimeTypes).contains(where: {
return mimeType.caseInsensitiveCompare($0.rawValue) == .orderedSame
})
}
}
public struct ImageMetadata {
public let imageFormat: ImageFormat
public let pixelSize: CGSize
public let hasAlpha: Bool
let isAnimated: Bool
internal init(imageFormat: ImageFormat, pixelSize: CGSize, hasAlpha: Bool, isAnimated: Bool) {
self.imageFormat = imageFormat
self.pixelSize = pixelSize
self.hasAlpha = hasAlpha
self.isAnimated = isAnimated
}
var hasStickerLikeProperties: Bool {
let maxStickerHeight = CGFloat(512)
return (
pixelSize.width <= maxStickerHeight
&& pixelSize.height <= maxStickerHeight
&& pixelSize != CGSize(width: 1, height: 1)
&& hasAlpha
)
}
}
internal struct WebpMetadata {
let isValid: Bool
let canvasWidth: UInt32
let canvasHeight: UInt32
let frameCount: UInt32
}
public enum ImageMetadataResult {
/// Source data exceeded size limit for all attachments;
/// as a precaution no validation was performed.
case genericSizeLimitExceeded
/// Exceeded the file size limit for the inferred type of image.
/// Smaller than the generic size limit.
case imageTypeSizeLimitExceeded
case invalid
case valid(ImageMetadata)
}