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

news/2024/7/20 20:01:44 标签: ios, pdf, 长图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.width
            let visible_height = visible_width * Board_height/Board_width
            let visibleSize = CGSize(width: visible_width, height: visible_height)
            let count:Int = Int(ceil(pdfSize.height/visible_height))// 计算需要分割的次数
            let index:Int
            let itemSize:CGSize
            if 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.height
                 itemSize = 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.originalPageRect
        let elaborate: CGFloat = 1.0
        let scale_W = pdfSize.width / originalPageRect.size.width * elaborate
        let scale_H = pdfSize.height / originalPageRect.size.height * elaborate
        let width = originalPageRect.size.width * scale_W
        let height = width * Board_height/Board_width // Board_height屏幕高度,Board_width屏幕宽度
        let visibleSize = CGSize(width: width, height: height)

        let rotation = ori_page.rotation
        ori_page.rotation = 0

        let 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:CGRect
        var tailHeight = 0.0
        if 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.height
            scaledPageRect = 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 in
                UIColor.white.set()
                //这个核心代码,scaledPageRect就是计算好的rect,
                ctx.fill(scaledPageRect)
                let rotationAngle: CGFloat
                switch 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.origin
                    dic[annotation] = annotation.bounds
                    let 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_bounds
                    annotation.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 = tmpImg
            tmpImg = nil
            
        }
        //将对pdfpage的修改进行还原
        ori_page.rotation = rotation
        for (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 up
        newSize.width = floor(newSize.width)
        newSize.height = floor(newSize.height)

        UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }

        // Move origin to middle
        context.translateBy(x: newSize.width/2, y: newSize.height/2)
        // Rotate around middle
        context.rotate(by: CGFloat(radians))
        // Draw the image at its center
        self.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.niftyadmin.cn/n/5338704.html

相关文章

领航分布式消息系统:一起探索Apache Kafka的核心术语及其应用场景

本文是Kafka系列文章的第一篇&#xff0c;将带你了解Kafka的核心术语及其应用场景&#xff0c;后续会逐步探索其各方面的原理及应用场景。下面先看一张大概得简图&#xff0c;涉及Kafka的功能、原理等等&#xff0c;后续不断深入介绍&#xff0c;欢迎关注。 1、什么是消息中间…

git clone超时

本文介绍作者在Centos上链接github超时&#xff0c;无法克隆的解决方案 在出现上图所示问题时&#xff0c;有可能是连接不到github.com&#xff0c;读者可以尝试输入ping github.com&#xff0c;当输入该指令后若长时间没有反应说明可能由于本地DNS无法解析导致的。 解决方案…

Google用AI替代广告销售工作只是开始……

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 前几天Google不是裁员3万人吗&#xff0c;其中有一个信息值得关注&#xff1a;就是Google的广告部门的部分员工&#xff0c;也被裁员了。 当然这不新鲜的&#xff0c;主要原因是Google的广告业务正在转向AI驱动了…

56. 合并区间 - 力扣(LeetCode)

问题描述 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 输入格式 intervals [[1,3],[2,6],[8,10],…

(蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)

能够表示为某个整数的平方的数字称为“平方数 虽然无法立即说出某个数是平方数&#xff0c;但经常可以断定某个数不是平方数。因为平方数的末位只可能是:0,1,4,5,6,9 这 6 个数字中的某个。所以&#xff0c;4325435332 必然不是平方数。 如果给你一个 2 位或 2 位以上的数字&am…

深度解析Python关键字:掌握核心语法的基石(新版本35+4)

目录 关键字 keyword 关键字列表 kwlist softkwlist 关键字分类 数据类型 True、False None 运算类型 and、or、not in is 模块导入 import 辅助关键字 from、as 上下文管理 with 占位语句 pass 流程控制 if、elif、else for while break、continue…

Spring Web文件上传功能简述

文章目录 正文简单文件上传文件写入 总结 正文 在日常项目开发过程中&#xff0c;文件上传是一个非常常见的功能&#xff0c;当然正规项目都有专门的文件服务器保存上传的文件&#xff0c;实际只需要保存文件路径链接到数据库中即可&#xff0c;但在小型项目中可能没有专门的文…

PG DBA培训26:PostgreSQL运维诊断与监控分析

本课程由风哥发布的基于PostgreSQL数据库的系列课程&#xff0c;本课程属于PostgreSQL Diagnosis and monitoring analysis&#xff0c;学完本课程可以掌握PostgreSQL日常运维检查-风哥PGSQL工具箱&#xff0c;风哥专用PGSQL工具箱介绍&#xff0c;风哥专用PGSQL工具箱使用&…