iOS/WAStickersThirdParty/ImageData.swift (123 lines of code) (raw):
//
// Copyright (c) WhatsApp Inc. and its affiliates.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
//
import UIKit
extension CGSize {
public static func ==(left: CGSize, right: CGSize) -> Bool {
return left.width.isEqual(to: right.width) && left.height.isEqual(to: right.height)
}
public static func <(left: CGSize, right: CGSize) -> Bool {
return left.width.isLess(than: right.width) && left.height.isLess(than: right.height)
}
public static func >(left: CGSize, right: CGSize) -> Bool {
return !left.width.isLessThanOrEqualTo(right.width) && !left.height.isLessThanOrEqualTo(right.height)
}
public static func <=(left: CGSize, right: CGSize) -> Bool {
return left.width.isLessThanOrEqualTo(right.width) && left.height.isLessThanOrEqualTo(right.height)
}
public static func >=(left: CGSize, right: CGSize) -> Bool {
return !left.width.isLess(than: right.width) && !left.height.isLess(than: right.height)
}
}
/**
* Represents the two supported extensions for sticker images: png and webp.
*/
enum ImageDataExtension: String {
case png = "png"
case webp = "webp"
}
/**
* Stores sticker image data along with its supported extension.
*/
class ImageData {
let data: Data
let type: ImageDataExtension
var bytesSize: Int64 {
return Int64(data.count)
}
/**
* Returns whether or not the data represents an animated image.
* It will always return false if the image is png.
*/
lazy var animated: Bool = {
if type == .webp {
return WebPManager.shared.isAnimated(webPData: data)
} else {
return false
}
}()
/**
* Returns the minimum frame duration for an animated image in milliseconds.
* It will always return -1 if the image is not animated.
*/
lazy var minFrameDuration: Double = {
return WebPManager.shared.minFrameDuration(webPData: data) * 1000
}()
/**
* Returns the total animation duration for an animated image in milliseconds.
* It will always return -1 if the image is not animated.
*/
lazy var totalAnimationDuration: Double = {
return WebPManager.shared.totalAnimationDuration(webPData: data) * 1000
}()
/**
* Returns the webp data representation of the current image. If the current image is already webp,
* the data is simply returned. If it's png, it will returned the webp converted equivalent data.
*/
lazy var webpData: Data? = {
if type == .webp {
return data
} else {
return WebPManager.shared.encode(pngData: data)
}
}()
/**
* Returns a UIImage of the current image data. If data is corrupt, nil will be returned.
*/
lazy var image: UIImage? = {
if type == .webp {
guard let images = WebPManager.shared.decode(webPData: data) else {
return nil
}
if images.count == 0 {
return nil
}
if images.count == 1 {
return images.first
}
return UIImage.animatedImage(with: images, duration: WebPManager.shared.totalAnimationDuration(webPData: data))
} else {
// Static image
return UIImage(data: data)
}
}()
/**
* Returns an image with the new size.
*/
func image(withSize size: CGSize) -> UIImage? {
guard let image = image else { return nil }
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
image.draw(in: CGRect(origin: .zero, size: size))
let resizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return resizedImage
}
init(data: Data, type: ImageDataExtension) {
self.data = data
self.type = type
}
static func imageDataIfCompliant(contentsOfFile filename: String, isTray: Bool) throws -> ImageData {
let fileExtension: String = (filename as NSString).pathExtension
guard let imageURL = Bundle.main.url(forResource: filename, withExtension: "") else {
throw StickerPackError.fileNotFound
}
let data = try Data(contentsOf: imageURL)
guard let imageType = ImageDataExtension(rawValue: fileExtension) else {
throw StickerPackError.unsupportedImageFormat(fileExtension)
}
return try ImageData.imageDataIfCompliant(rawData: data, extensionType: imageType, isTray: isTray)
}
static func imageDataIfCompliant(rawData: Data, extensionType: ImageDataExtension, isTray: Bool) throws -> ImageData {
let imageData = ImageData(data: rawData, type: extensionType)
guard imageData.bytesSize > 0 else {
throw StickerPackError.invalidImage
}
if isTray {
guard !imageData.animated else {
throw StickerPackError.animatedImagesNotSupported
}
guard imageData.bytesSize <= Limits.MaxTrayImageFileSize else {
throw StickerPackError.imageTooBig(imageData.bytesSize, false)
}
guard imageData.image!.size == Limits.TrayImageDimensions else {
throw StickerPackError.incorrectImageSize(imageData.image!.size)
}
} else {
let isAnimated = imageData.animated
guard (isAnimated && imageData.bytesSize <= Limits.MaxAnimatedStickerFileSize) ||
(!isAnimated && imageData.bytesSize <= Limits.MaxStaticStickerFileSize) else {
throw StickerPackError.imageTooBig(imageData.bytesSize, isAnimated)
}
guard imageData.image!.size == Limits.ImageDimensions else {
throw StickerPackError.incorrectImageSize(imageData.image!.size)
}
if isAnimated {
guard imageData.minFrameDuration >= Double(Limits.MinAnimatedStickerFrameDurationMS) else {
throw StickerPackError.minFrameDurationTooShort(imageData.minFrameDuration)
}
guard imageData.totalAnimationDuration <= Double(Limits.MaxAnimatedStickerTotalDurationMS) else {
throw StickerPackError.totalAnimationDurationTooLong(imageData.totalAnimationDuration)
}
}
}
return imageData
}
}