iOS长图生成的pdf性能优化记录

背景

  某日产品拿来了一个由30多页高清长图生成的pdf,在应用中运行出现了崩溃。

排查

  经过调试发现加载长图生成的pdf时,运行内存会出现缓慢增长,直至崩溃。经过代码定位发现时pdf转成image对象的过程中由于是长图生成的pdf,这一页的pdf的size相当于正常pdfsize的30多页,转换的过程中context的fill的size也是正常pdf的30多倍。经过调研,尝试,发现对于同一页的pdf,可以通过调整context的fill的size来只把pdf中的部分内容转换成image对象,内存正常也不大。

方案

  原来的方案是每页pdf生成一个image对象,通过一个collectonViewCell来显示。调整后的方案为:根据屏幕大小来决定一个pdf页面生成多少个image对象,有多少个image对象,一个section里就有多少个cell。方案如下:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {if pdfSize.height/pdfSize.width >= 3.0 { //长图生成的pdf 备注:pdfSize 即一页pdf的大小let visible_width = pdfSize.widthlet visible_height = visible_width * Board_height/Board_widthlet visibleSize = CGSize(width: visible_width, height: visible_height)let count:Int = Int(ceil(pdfSize.height/visible_height))// 计算需要分割的次数let index:Intlet itemSize:CGSizeif indexPath.item < count {index = indexPath.item} else {index = count - 1}if CGFloat(index) * visibleSize.height + visibleSize.height < pdfSize.height {itemSize = visibleSize} else {let tailHeight = pdfSize.height - CGFloat(index) * visibleSize.heightitemSize = CGSize(width: visibleSize.width, height: tailHeight)}return itemSize}return pdfSize}

在讲pdf转成image对象的过程中,方案调整核心代码如下:

//index就是pdf中的从上往下被分割后的image的索引值private func subImageFromLongPDF(ori_page:PDFPage, pdfSize:CGSize, index:Int) -> UIImage {guard let page = ori_page.pageRef else {return UIImage()}var dic = [PDFAnnotation:CGRect]()let originalPageRect = ori_page.originalPageRectlet elaborate: CGFloat = 1.0let scale_W = pdfSize.width / originalPageRect.size.width * elaboratelet scale_H = pdfSize.height / originalPageRect.size.height * elaboratelet width = originalPageRect.size.width * scale_Wlet height = width * Board_height/Board_width // Board_height屏幕高度,Board_width屏幕宽度let visibleSize = CGSize(width: width, height: height)let rotation = ori_page.rotationori_page.rotation = 0let scaledOrigin = CGPoint(x: originalPageRect.origin.x * scale_W, y: originalPageRect.origin.y * scale_H)let scaledPageSize = CGSize(width: originalPageRect.size.width * scale_W, height: originalPageRect.size.height * scale_H)let scaledPageRect:CGRectvar tailHeight = 0.0if CGFloat(index) * visibleSize.height + height < scaledPageSize.height {scaledPageRect = CGRect(origin: CGPoint(x: 0, y: CGFloat(index) * visibleSize.height), size: visibleSize)} else {tailHeight = scaledPageSize.height - CGFloat(index) * visibleSize.heightscaledPageRect = CGRect(origin: CGPoint(x: 0, y: CGFloat(index) * visibleSize.height), size: CGSize(width: visibleSize.width, height: tailHeight))}var img:UIImage?autoreleasepool {let renderer = UIGraphicsImageRenderer(size: scaledPageRect.size)var tmpImg:UIImage? = renderer.image {ctx inUIColor.white.set()//这个核心代码,scaledPageRect就是计算好的rect,ctx.fill(scaledPageRect)let rotationAngle: CGFloatswitch page.rotationAngle {//保持和安卓一致,强制为0了case 90:rotationAngle = 270//平移 以用户空间为单位,指定上下文的坐标空间 x 轴的位移量。ctx.cgContext.translateBy(x: -scaledPageRect.origin.x, y: 0)case 180:rotationAngle = 180//平移ctx.cgContext.translateBy(x: scaledPageRect.width,y: 0)case 270:rotationAngle = 90//平移ctx.cgContext.translateBy(x: scaledPageRect.origin.x, y: scaledPageRect.size.height - scaledPageRect.origin.y)default:rotationAngle = 0//平移 以用户空间为单位,指定上下文的坐标空间 x 轴的位移量。//指定上下文的坐标空间 y 轴的位移量(以用户空间为单位)。if rotation == 180 {if tailHeight > 0 {//尾部不足一屏的特殊处理逻辑ctx.cgContext.translateBy(x: 0 - scaledOrigin.x, y: 0 + scaledOrigin.y + CGFloat(index+1) * visibleSize.height - (visibleSize.height - tailHeight))//翻转180度正常} else {ctx.cgContext.translateBy(x: 0 - scaledOrigin.x, y: 0 + scaledOrigin.y + CGFloat(index+1) * visibleSize.height)//翻转180度正常}} else {ctx.cgContext.translateBy(x: 0 - scaledOrigin.x, y: scaledPageSize.height + scaledOrigin.y - scaledPageRect.origin.y)}}//Rotate是以原点为圆心旋转,Quartz创建的图形上下文旋转圆心为左下角,角度值正数为逆时针旋转,负数为顺时针旋转//UIKit创建的图像上下文旋转圆心为左上角,角度值正数为顺时针旋转,负数为逆时针旋转。// Flip the context vertically because the Core Graphics coordinate system starts from the bottom.ctx.cgContext.scaleBy(x:1.0, y: -1.0)//垂直翻转上下文,因为核心图形坐标系从底部开始//旋转 正值逆时针旋转,负值顺时针旋转ctx.cgContext.rotate(by: rotationAngle.degreesToRadians)ctx.cgContext.scaleBy(x: scale_W, y: scale_H)//缩放// Draw the PDF page.// 此处仍然是正常的绘制pdf,因为前面设置了context的fillSize,因此pdf绘制的时候只在前面指定的rect才会生效。ctx.cgContext.drawPDFPage(page)for annotation in ori_page.annotations {let origin = annotation.bounds.origindic[annotation] = annotation.boundslet annotation_fill_bounds = CGRect(x: origin.x + originalPageRect.origin.x, y: origin.y + originalPageRect.origin.y, width: annotation.bounds.size.width, height: annotation.bounds.size.height)annotation.bounds = annotation_fill_boundsannotation.draw(with: .cropBox, in: ctx.cgContext)}}if rotation%360 != 0 {let scale:Float =  Float(rotation) / Float(180)tmpImg = tmpImg?.rotate(radians: Float.pi * scale) ?? UIImage.init()}img = tmpImgtmpImg = nil}//将对pdfpage的修改进行还原ori_page.rotation = rotationfor (annotation,bounds) in dic {annotation.bounds = bounds}return img ?? UIImage()}extension PDFPage {var originalPageRect: CGRect {switch rotation {case 90, 270:let originalRect = bounds(for: PDFDisplayBox.cropBox)let rotatedSize = CGSize(width: originalRect.height, height: originalRect.width)return CGRect(origin: originalRect.origin, size: rotatedSize)default:return bounds(for: PDFDisplayBox.cropBox)}}
}extension UIImage {func rotate(radians: Float) -> UIImage? {var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size// Trim off the extremely small float value to prevent core graphics from rounding it upnewSize.width = floor(newSize.width)newSize.height = floor(newSize.height)UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)guard let context = UIGraphicsGetCurrentContext() else {return nil}// Move origin to middlecontext.translateBy(x: newSize.width/2, y: newSize.height/2)// Rotate around middlecontext.rotate(by: CGFloat(radians))// Draw the image at its centerself.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))let newImage = UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()return newImage}
}extension FloatingPoint {var degreesToRadians: Self { return self * .pi / 180 }var radiansToDegrees: Self { return self * 180 / .pi }
}

备注:这段代码结合了自己的项目实际业务,大家作为参考。多看下核心代码和注释

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/639101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

海外抖音TikTok、正在内测 AI 生成歌曲功能,依靠大语言模型 Bloom 进行文本生成歌曲

近日&#xff0c;据外媒The Verge报道&#xff0c;TikTok正在测试一项新功能&#xff0c;利用大语言模型Bloom的AI能力&#xff0c;允许用户上传歌词文本&#xff0c;并使用AI为其添加声音。这一创新旨在为用户提供更多创作音乐的工具和选项。 Bloom 是由AI初创公司Hugging Fac…

使用torch求函数参数

一、先了解一下深度学习是如何优化参数的 import torch import mathclass Polynomial3(torch.nn.Module):def __init__(self):super().__init__()self.a torch.nn.Parameter(torch.randn(()))self.b torch.nn.Parameter(torch.randn(()))self.c torch.nn.Parameter(torch.r…

C语言——内存函数介绍和模拟实现(memcpy、memmove、memset、memcmp)

之前我们讲过一些字符串函数&#xff08;http://t.csdnimg.cn/ZcvCo&#xff09;&#xff0c;今天我们来讲一讲几个内存函数&#xff0c;那么可能有人要问了&#xff0c;都有字符串函数了&#xff0c;怎么又来个内存函数&#xff0c;这不是一样的么&#xff1f; 我们要知道之前…

第十二站(20天):C++泛型编程

模板 C提供了模板(template)编程的概念。所谓模板&#xff0c;实际上是建立一个通用函数或类&#xff0c; 其 类内部的类型和函数的形参类型不具体指定 &#xff0c;用一个虚拟的类型来代表。这种通用的方式称 为模板。 模板是泛型编程的基础, 泛型编程即以一种独立于任何特定…

Java快速转Go入门案例

Golang语言在2009年诞生于谷歌&#xff0c;相较而言是一门年轻的语言。面对C等老牌语言众多繁重的特性&#xff0c;几名谷歌员工希望能够甩开历史包袱设计一门更加简洁的编程语言&#xff0c;避免过度的设计&#xff0c;通过较少的特性组合连接就可实现复杂的功能。体现“少即是…

C++面试:跳表

目录 跳表介绍 跳表的特点&#xff1a; 跳表的应用场景&#xff1a; C 代码示例&#xff1a; 跳表的特性 跳表示例 总结 跳表&#xff08;Skip List&#xff09;是一种支持快速搜索、插入和删除的数据结构&#xff0c;具有相对简单的实现和较高的查询性能。下面是跳表…

巨变!如何理解中国发起的“数据要素X”计划?

作者 张群&#xff08;赛联区块链教育首席讲师&#xff0c;工信部赛迪特聘资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09;关注张群&#xff0c;为您提供一站式区块链技术和方案咨询。 刘烈宏在第25届北大光华新…

自定义shell工具函数之echo_red()和prepare_check_required_pkg()

两个 bash 函数&#xff0c;echo_red 和 prepare_check_required_pkg&#xff0c;它们在 shell 脚本中使用。下面是每个函数的解释&#xff1a; echo_red 函数 function echo_red() {echo -e "\033[1;31m$1\033[0m" }这个函数的目的是输出红色的文本到终端。它接受…

职业规划,软件开发工程师的岗位任职资格

软件工程师是指从事软件开发的人&#xff0c;主要的工作涉及到项目培训和项目设计两个方面。在实际工作中&#xff0c;软件工程师是一个广义的概念&#xff0c;包括了很多与软件相关的人员。除开最基础的编程语言&#xff0c;还有数据库语言等等。从事这份工作&#xff0c;需要…

记录一下uniapp 集成腾讯im特别卡(已解决)

uniapp的项目运行在微信小程序 , 安卓 , ios手机三端 , 之前这个项目集成过im,不过版本太老了,0.x的版本, 现在需要添加客服功能,所以就升级了 由于是二开 , 也为了方便 , 沿用之前的webview嵌套腾讯IM的方案 , 选用uniapp集成ui ,升级之后所有安卓用户反馈点击进去特别卡,几…

【cucumber】学习跟踪

本文章主要跟踪cucumber自动化测试的学习进度&#xff0c;并且统计文章目录&#xff1a; 【cucumber】cluecumber-report-plugin生成测试报告-CSDN博客

openssl3.2 - 官方demo学习 - test - certs - 001 - Primary root: root-cert

文章目录 openssl3.2 - 官方demo学习 - test - certs - 001 - Primary root: root-cert概述笔记官方原版.sh实验自己来分3步走做实验my_openssl_log_001_doc_1.txtmy_openssl_log_001_doc_2.txtmy_openssl_log_001_doc_3.txt备注END openssl3.2 - 官方demo学习 - test - certs …

Java设计模式之观察者模式详解

Java设计模式之观察者模式详解 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我们将一同深入探讨Java设计模式之观察者模式&#xff0c;这是一种代…

HR人才测评,如何做技术研发人员基本素质测评?

技术研发人员的基本素质测评&#xff0c;可以从以下几个方面考虑&#xff1a; 1. 技术能力&#xff1a;首要的因素是技术能力&#xff0c;包括编程能力、算法能力、架构设计能力、代码调试和优化能力等。在测评中可以通过技术测试、编程练习、项目经验等方式来考察。 2. 学习…

微前端:一种新型的前端架构方法

微前端是一种新型的前端架构方法&#xff0c;它将单页面应用程序拆分为一系列小型、独立的应用程序。本文将介绍微前端的背景、概念、特点、应用场景和与其他前端架构的对比&#xff0c;以帮助读者更好地理解微前端的概念和实践。 一、引言 随着Web应用程序的复杂性和规模不断…

Java - 深入理解加密解密和签名算法

文章目录 应用的接口安全性问题可能来源加密解密Why保护数据隐私防止未经授权的访问防止数据泄露 对称加密 VS 单向加密 VS 非对称加密一、对称加密二、单向加密&#xff08;哈希加密&#xff09;三、非对称加密 常用的对称加密算法1. AES&#xff08;高级加密标准&#xff09;…

Django从入门到精通(二)

目录 三、视图 3.1、文件or文件夹 3.2、相对和绝对导入urls 3.3、视图参数requests 3.4、返回值 3.5、响应头 3.6、FBV和CBV FBV 四、静态资源 4.1、静态文件 4.2、媒体文件 五、模板 5.1、寻找html模板 5.2、模板处理的本质 5.3、常见模板语法 5.4、内置模板函…

【DevOps-08-5】目标服务器准备脚本,并基于Harbor的最终部署

一、简要描述 告知目标服务器拉取哪个镜像判断当前服务器是否正在运行容器,停止并删除如果目标服务器已经存在当前镜像,删除当前版本的镜像目标服务器拉取Harbor上的镜像将拉取下来的镜像运行成容器二、准备目标服务器脚本文件 1、在部署的目标服务器准备deploy.sh部署脚本 …

点云从入门到精通技术详解100篇-基于点云和图像融合的智能驾驶目标检测(中)

目录 2.1.2 数据源选型分析 2.2 环境感知系统分析 2.2.1 传感器布置方案分析

对称密码算法有什么优点

对称密码算法是一种加密和解密数据的方法&#xff0c;其中加密和解密使用相同的密钥。这种方法的一个关键特点是加密和解密的速度非常快&#xff0c;因此它在许多需要高速加密的应用中非常有用。 对称密码算法的优点主要在于其效率和安全性。由于加密和解密使用相同的密钥&…