diff --git a/MiniScanner.xcodeproj/project.pbxproj b/MiniScanner.xcodeproj/project.pbxproj index 53b844d0751001e57d0bac1a8faca81657103da0..25ca052a30d28186ba5ac42d5d81bdd87c90806f 100644 --- a/MiniScanner.xcodeproj/project.pbxproj +++ b/MiniScanner.xcodeproj/project.pbxproj @@ -238,6 +238,8 @@ 678BD7172C4CF1EB00833DA5 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD7122C4CF1EB00833DA5 /* SettingsViewModel.swift */; }; 678BD71D2C4D057200833DA5 /* UIImage+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD71C2C4D057200833DA5 /* UIImage+Images.swift */; }; 678BD71F2C4D07B100833DA5 /* Image+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678BD71E2C4D07B100833DA5 /* Image+Images.swift */; }; + 6794328C2C689E9F002E5F8D /* WXCompress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6794328B2C689E9F002E5F8D /* WXCompress.swift */; }; + 6794328F2C68A991002E5F8D /* mozjpeg in Frameworks */ = {isa = PBXBuildFile; productRef = 6794328E2C68A991002E5F8D /* mozjpeg */; }; 67959CC42C566B7A00CAB102 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67959CC32C566B7A00CAB102 /* Icons.xcassets */; }; 67A20DDD2C57A142009D2F25 /* DocumentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A20DDC2C57A142009D2F25 /* DocumentLayout.swift */; }; 67A20DE12C57BC56009D2F25 /* DocumentsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A20DDF2C57BC56009D2F25 /* DocumentsCollectionViewCell.swift */; }; @@ -245,6 +247,7 @@ 67A20DE42C57BC89009D2F25 /* DocumentsCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A20DE32C57BC89009D2F25 /* DocumentsCellDelegate.swift */; }; 67D714B52C5161A30065E6F4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67D714B42C5161A30065E6F4 /* Images.xcassets */; }; 67E6A1962C64DEB400A77F29 /* ScannedItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6A1952C64DEB400A77F29 /* ScannedItemType.swift */; }; + 67E6A1982C65151F00A77F29 /* ImageCompressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6A1972C65151F00A77F29 /* ImageCompressView.swift */; }; B827E5196CC419E773B843E1 /* Pods_MiniScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */; }; EC0CF1FE254D8BBF00888722 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FD254D8BBF00888722 /* AppDelegate.swift */; }; EC0CF200254D8BBF00888722 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FF254D8BBF00888722 /* SceneDelegate.swift */; }; @@ -529,6 +532,7 @@ 678BD7122C4CF1EB00833DA5 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; }; 678BD71C2C4D057200833DA5 /* UIImage+Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Images.swift"; sourceTree = "<group>"; }; 678BD71E2C4D07B100833DA5 /* Image+Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Images.swift"; sourceTree = "<group>"; }; + 6794328B2C689E9F002E5F8D /* WXCompress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WXCompress.swift; sourceTree = "<group>"; }; 67959CC32C566B7A00CAB102 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = "<group>"; }; 67A20DDC2C57A142009D2F25 /* DocumentLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentLayout.swift; sourceTree = "<group>"; }; 67A20DDF2C57BC56009D2F25 /* DocumentsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsCollectionViewCell.swift; sourceTree = "<group>"; }; @@ -536,6 +540,7 @@ 67A20DE32C57BC89009D2F25 /* DocumentsCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsCellDelegate.swift; sourceTree = "<group>"; }; 67D714B42C5161A30065E6F4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; 67E6A1952C64DEB400A77F29 /* ScannedItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedItemType.swift; sourceTree = "<group>"; }; + 67E6A1972C65151F00A77F29 /* ImageCompressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompressView.swift; sourceTree = "<group>"; }; E8AF4FB39674DF589D719DCF /* Pods-MiniScanner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MiniScanner.release.xcconfig"; path = "Target Support Files/Pods-MiniScanner/Pods-MiniScanner.release.xcconfig"; sourceTree = "<group>"; }; E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MiniScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC0CF1FA254D8BBF00888722 /* MiniScanner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiniScanner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -583,6 +588,7 @@ 539D1C972C171344009DB24A /* LNExtensionExecutor in Frameworks */, 539996542C2711BA00671340 /* ZLImageEditor in Frameworks */, 53A4788A2C358FFA0073E956 /* ImageViewer_swift in Frameworks */, + 6794328F2C68A991002E5F8D /* mozjpeg in Frameworks */, 53A478812C358E100073E956 /* LanguageManager-iOS in Frameworks */, B827E5196CC419E773B843E1 /* Pods_MiniScanner.framework in Frameworks */, ); @@ -1395,6 +1401,15 @@ path = Settings; sourceTree = "<group>"; }; + 6794328A2C689E78002E5F8D /* Compress */ = { + isa = PBXGroup; + children = ( + 67E6A1972C65151F00A77F29 /* ImageCompressView.swift */, + 6794328B2C689E9F002E5F8D /* WXCompress.swift */, + ); + path = Compress; + sourceTree = "<group>"; + }; 67A20DDE2C57BC32009D2F25 /* DocumentsCollectionViewCell */ = { isa = PBXGroup; children = ( @@ -1541,6 +1556,7 @@ EC8A9B2C254DE94900F9AF99 /* DocumentPreview */ = { isa = PBXGroup; children = ( + 6794328A2C689E78002E5F8D /* Compress */, ECA1FAA0254DEA6A0081F00B /* DocumentPreview.storyboard */, EC8A9B26254DE91B00F9AF99 /* DocumentPreviewViewController.swift */, EC702521254DF13200BE1958 /* PencilKitViewController.swift */, @@ -1597,6 +1613,7 @@ 53A478862C358F4A0073E956 /* Toast */, 53A478892C358FFA0073E956 /* ImageViewer_swift */, 6708A6032C50FCA50036805D /* EPSignature */, + 6794328E2C68A991002E5F8D /* mozjpeg */, ); productName = MiniScanner; productReference = EC0CF1FA254D8BBF00888722 /* MiniScanner.app */; @@ -1636,6 +1653,7 @@ 53A478852C358F4A0073E956 /* XCRemoteSwiftPackageReference "Toast-Swift" */, 53A478882C358FFA0073E956 /* XCRemoteSwiftPackageReference "ImageViewer" */, 6708A6022C50FCA50036805D /* XCRemoteSwiftPackageReference "BeInEPSignature" */, + 6794328D2C68A991002E5F8D /* XCRemoteSwiftPackageReference "mozjpeg.swift" */, ); productRefGroup = EC0CF1FB254D8BBF00888722 /* Products */; projectDirPath = ""; @@ -1917,9 +1935,11 @@ 53014F962C11A8E80071CE39 /* ScannerViewController.swift in Sources */, 53014FA52C11A8E80071CE39 /* EditScanCornerView.swift in Sources */, 677E65EE2C5A6C0A0039E2C5 /* SessionModel.xcdatamodeld in Sources */, + 6794328C2C689E9F002E5F8D /* WXCompress.swift in Sources */, 53E7D3382C1B00880025A1D3 /* FSPagerViewLayoutAttributes.swift in Sources */, 53014F8E2C11A8E80071CE39 /* ImageScannerController.swift in Sources */, 677E65E92C5A36A40039E2C5 /* UpdateScanSessionUseCase.swift in Sources */, + 67E6A1982C65151F00A77F29 /* ImageCompressView.swift in Sources */, 539996A42C27130000671340 /* ConstraintMakerRelatable.swift in Sources */, 677E65C92C5A1A7D0039E2C5 /* ScanMapper.swift in Sources */, 678BD7162C4CF1EB00833DA5 /* SettingsViewCoordinator.swift in Sources */, @@ -2220,6 +2240,14 @@ kind = branch; }; }; + 6794328D2C68A991002E5F8D /* XCRemoteSwiftPackageReference "mozjpeg.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/awxkee/mozjpeg.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.3; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -2258,6 +2286,11 @@ package = 6708A6022C50FCA50036805D /* XCRemoteSwiftPackageReference "BeInEPSignature" */; productName = EPSignature; }; + 6794328E2C68A991002E5F8D /* mozjpeg */ = { + isa = XCSwiftPackageProductDependency; + package = 6794328D2C68A991002E5F8D /* XCRemoteSwiftPackageReference "mozjpeg.swift" */; + productName = mozjpeg; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/MiniScanner/Modules/DocumentPreview/Compress/ImageCompressView.swift b/MiniScanner/Modules/DocumentPreview/Compress/ImageCompressView.swift new file mode 100644 index 0000000000000000000000000000000000000000..7fdf858c30a73f51eb29c72144e24daad5e48e41 --- /dev/null +++ b/MiniScanner/Modules/DocumentPreview/Compress/ImageCompressView.swift @@ -0,0 +1,246 @@ +// +// ImageCompressView.swift +// MiniScanner +// +// Created by Mustafa Merza on 8/8/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI +import mozjpeg + +struct ImageCompressView: View { + + var image: UIImage + + var columns = [GridItem(.flexible()), GridItem(.flexible())] + + @State private var images: [[UIImage]] = Array(repeating: Array(repeating: UIImage(), count: 4), count: 4) + + init(image: UIImage) { + self.image = image + } + + var body: some View { + ScrollView(showsIndicators: false) { + + VStack(spacing: 16) { + + imagesGrid("") { + + original + + uiImage1 + + timeline1 + + mozjpeg1 + } + + imagesGrid("") { + + original + + uiImage2 + + timeline2 + + mozjpeg2 + } + + imagesGrid("") { + + original + + uiImage3 + + timeline3 + + mozjpeg3 + } + + imagesGrid("Dec") { + + original + + decMozjpeg1 + + decMozjpeg2 + + decMozjpeg3 + } + } + .padding(.all, 8) + } + .task { + await prepareImages() + } + } + + private func prepareImages() async { + + images[0][0] = image + images[1][0] = image + images[2][0] = image + + images[0][1] = compressImages(image: image, compressionQuality: 1) + images[1][1] = compressImages(image: image, compressionQuality: 0.5) + images[2][1] = compressImages(image: image, compressionQuality: 0) + + images[0][2] = image.wxCompress() + images[1][2] = image.wxCompress().wxCompress() + images[2][2] = image.wxCompress().wxCompress().wxCompress() + + images[0][3] = compressImagesWithMozJpeg(image: image, quality: 1) + images[1][3] = compressImagesWithMozJpeg(image: image, quality: 0.5) + images[2][3] = compressImagesWithMozJpeg(image: image, quality: 0) + + images[3][0] = decompressImagesWithMozJpeg(image: image, quality: 1) + images[3][1] = decompressImagesWithMozJpeg(image: image, quality: 0.5) + images[3][2] = decompressImagesWithMozJpeg(image: image, quality: 0) + } + + private var original: some View { + imageView(image: image, + title: "Original") + } + + private var uiImage1: some View { + imageView(image: images[0][1], + title: "UI Q 1") + } + + private var uiImage2: some View { + imageView(image: images[1][1], + title: "UI Q 0.5") + } + + private var uiImage3: some View { + imageView(image: images[2][1], + title: "UI Q 0") + } + + private var timeline1: some View { + imageView(image: images[0][2], + title: "Timeline x1") + } + + private var timeline2: some View { + imageView(image: images[1][2], + title: "Timeline x2") + } + + private var timeline3: some View { + imageView(image: images[2][2], + title: "Timeline x3") + } + + private var mozjpeg1: some View { + imageView(image: images[0][3], + title: "Moz Q 1") + } + + private var mozjpeg2: some View { + imageView(image: images[1][3], + title: "Moz Q 0.5") + } + + private var mozjpeg3: some View { + imageView(image: images[2][3], + title: "Moz Q 0") + } + + private var decMozjpeg1: some View { + imageView(image: images[3][0], + title: "DeMoz Q 1") + } + + private var decMozjpeg2: some View { + imageView(image: images[3][1], + title: "DeMoz Q 0.5") + } + + private var decMozjpeg3: some View { + imageView(image: images[3][2], + title: "DeMoz Q 0") + } +} + +extension ImageCompressView { + + private func imagesGrid(_ title: String, @ViewBuilder content: () -> some View) -> some View { + VStack(spacing: 2) { + + Text(title) + + LazyVGrid(columns: columns, alignment: .center) { + content() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + + private func imageView(image: UIImage, title: String) -> some View { + VStack { + + Text(title) + + Text(size(image: image)) + + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + } + } + + private func size(image: UIImage) -> String { + + var size = "" + + if let sizeInByte = image.jpegData(compressionQuality: 1)?.count { + + let sizeInMByte = Double(sizeInByte) / 1024.0 / 1024.0 + + size = String(format: "%.4f MB", sizeInMByte) + } + + return size + } +} + +extension ImageCompressView { + + private func compressImages(image: UIImage, compressionQuality: CGFloat) -> UIImage { + + if let data = image.jpegData(compressionQuality: compressionQuality), + let newImage = UIImage(data: data) { + return newImage + } + + return image + } + + private func compressImagesWithMozJpeg(image: UIImage, quality: Float) -> UIImage { + + if let data = try? image.mozjpegRepresentation(quality: quality), + let newImage = UIImage(data: data) { + return newImage + } + + return image + } + + private func decompressImagesWithMozJpeg(image: UIImage, quality: Float) -> UIImage { + + if let data = try? image.mozjpegRepresentation(quality: quality), + let newImage = Mozjpeg().decompress(chunk: data) { + return newImage + } + + return image + } +} + +#Preview { + ImageCompressView(image: .actions) +} diff --git a/MiniScanner/Modules/DocumentPreview/Compress/WXCompress.swift b/MiniScanner/Modules/DocumentPreview/Compress/WXCompress.swift new file mode 100644 index 0000000000000000000000000000000000000000..c86252fca805b4e822831a1f644c53372361e2cd --- /dev/null +++ b/MiniScanner/Modules/DocumentPreview/Compress/WXCompress.swift @@ -0,0 +1,97 @@ +// +// WXCompress.swift +// MiniScanner +// +// Created by Mustafa Merza on 8/11/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import Foundation + +public enum WechatCompressType { + case session + case timeline +} + +public extension UIImage { + + /** + wechat image compress + + - parameter type: session image boundary is 800, timeline is 1280 + + - returns: thumb image + */ + func wxCompress(type: WechatCompressType = .timeline) -> UIImage { + let size = self.wxImageSize(type: type) + let reImage = resizedImage(size: size) + let data = reImage.jpegData(compressionQuality: 0.5)! + return UIImage.init(data: data)! + } + + /** + get wechat compress image size + + - parameter type: session / timeline + + - returns: size + */ + private func wxImageSize(type: WechatCompressType) -> CGSize { + var width = self.size.width + var height = self.size.height + + var boundary: CGFloat = 1280 + + // width, height <= 1280, Size remains the same + guard width > boundary || height > boundary else { + return CGSize(width: width, height: height) + } + + // aspect ratio + let s = max(width, height) / min(width, height) + if s <= 2 { + // Set the larger value to the boundary, the smaller the value of the compression + let x = max(width, height) / boundary + if width > height { + width = boundary + height = height / x + } else { + height = boundary + width = width / x + } + } else { + // width, height > 1280 + if min(width, height) >= boundary { + boundary = type == .session ? 800 : 1280 + // Set the smaller value to the boundary, and the larger value is compressed + let x = min(width, height) / boundary + if width < height { + width = boundary + height = height / x + } else { + height = boundary + width = width / x + } + } + } + return CGSize(width: width, height: height) + } + + /** + Zoom the picture to the specified size + + - parameter newSize: image size + + - returns: new image + */ + private func resizedImage(size: CGSize) -> UIImage { + let newRect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + var newImage: UIImage! + UIGraphicsBeginImageContext(newRect.size) + newImage = UIImage(cgImage: self.cgImage!, scale: 1, orientation: self.imageOrientation) + newImage.draw(in: newRect) + newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return newImage + } +}