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 } }