iOS 超大高清圖展示策略 TileLayer 及 levelsOfDetailBias 分析

来源:http://www.cnblogs.com/v-jing/archive/2017/12/08/8005224.html
-Advertisement-
Play Games

本次分析針對當下流行的中國地圖圖片處理,1億像素,就是下麵這張: 原圖尺寸:11935x8554 文件大小:22.1MB 原始載入方式 首先,我們嘗試一下直接載入的方式,看看效果會有多恐怖 首先,我們嘗試一下直接載入的方式,看看效果會有多恐怖 效果請看下麵的Gif動畫展示: 直接載入原圖記憶體占用 可 ...


本次分析針對當下流行的中國地圖圖片處理,1億像素,就是下麵這張:

原圖尺寸:11935x8554 文件大小:22.1MB

 

 

原始載入方式

首先,我們嘗試一下直接載入的方式,看看效果會有多恐怖 效果請看下麵的Gif動畫展示:  

 

直接載入原圖記憶體占用

  可以看到載入時記憶體的瞬間峰值達到了481M,毫無疑問,記憶體暴增,直接崩潰(視機型而定,好一點的機器比如iphone7可以正常展示~) 代碼很簡單,一個UIImageView,直接設置image:  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        self.largeImage = [UIImage imageWithContentsOfFile:_path];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = self.largeImage;
        });
    });

 

分塊載入方式

好,主角上場~CATiledLayer,先看一下使用之後的效果吧,Gif動畫展示如下    

分塊載入效果 

  CATiledLayer是為載入大圖造成的性能問題提供的一個解決方案,他將需要繪製的內容分割成許多小塊,然後在許多線程里按需非同步繪製相應的小塊,具體如何劃分小塊和縮放時的載入策略,與CATiledLayer三個重要屬性有關, 一句話描述:tile layer設置一個縮放區域的集合和重繪閾值,讓scroll view在縮放時,繪製層根據這些區域和縮放閾值去重新繪製當前顯示的區域 CATiledLayer的核心工作原則就是圍繞這三個屬性開展, 官方解釋如下(不喜可先略過此處解釋):

 levelsOfDetail

The number of levels of detail maintained by this layer. Defaults to one. Each LOD is half the resolution of the previous level. If too many levels are specified for the current size of the layer, then the number of levels is clamped to the maximum value (the bottom most LOD must contain at least a single pixel in each dimension).

簡單來說就是從UIScrollView的縮小級別重繪設置,每一個層級的解析度是上一層級的½

 

 

levelsOfDetailBias The number of magnified levels of detail for this layer. Defaults t zero. Each previous level of detail is twice the resolution of the later. E.g. specifying 'levelsOfDetailBias' of two means that the layer devotes two of its specified levels of detail to magnification, i.e. 2x and 4x.

簡單來說就是,layer的放大級別重繪設置,每一個層級解析度是後面層級的2倍 不急,以上兩個屬性,我在後面會有詳細的描述

 

 

tileSize The maximum size of each tile used to create the layer's content. Defaults to (256, 256). Note that there is a maximum tile size, and requests for tiles larger than that limit will cause a suitable value to be substituted.

這個就簡單了,是layer劃分視圖區域最大尺寸

 

下麵的文章分析很透徹:

CATiledLayer (Part 2)

iOS開發 - CocoaChina CocoaChina_讓移動開發更簡單

但是我測試的結果和文章中的結論有比較大的出入,但是方向是一致的:(稍後我會做一些實驗展示tileSize和levelsOfDetailBias的關係)

 

分塊載入實現

代碼實現的核心,也是針對上面所提到的3個屬性和scrollview的zoomscale設置,當然,這裡面也走過一些彎路,下麵簡要介紹實現方式和中途遇到的坑   首先出場的是ViewController根頁面,沒什麼特別的:點擊按鈕,載入圖片  
- (void)btnTapped:(id)btnTapped {
    [self.scrollView removeFromSuperview];
    self.largeImage = [UIImage imageWithContentsOfFile:_path];

    self.scrollView = [[ImageScrollViewNoSlip alloc] initWithFrame:self.view.bounds
                                                             image:self.largeImage];

    [self.view addSubview:self.scrollView];

}

  

 看到上面的代碼,就知道重點肯定在這個ImageScrollViewNoSlip了,其實這個view只是一個UIscrollview,負責圖片視圖的縮放和位置更新,與我們通常使用圖片縮放的手法無異,只是對縮放繫數的設置做了簡單計算,如果對縮放繫數要求不是很細緻,可以直接忽略計算過程,初始化內容如下:

-(id)initWithFrame:(CGRect)frame image:(UIImage*)img {
    if((self = [super initWithFrame:frame])) {        
        // Set up the UIScrollView
        self.showsVerticalScrollIndicator = NO;
        self.showsHorizontalScrollIndicator = NO;
        self.bouncesZoom = YES;
        self.decelerationRate = UIScrollViewDecelerationRateFast;
        self.delegate = self;
    self.backgroundColor = [UIColor colorWithRed:0.4f green:0.2f blue:0.2f alpha:1.0f];

        // 根據圖片實際尺寸和屏幕尺寸計算圖片視圖的尺寸
        self.image = img;
        CGRect imageRect = CGRectMake(
                0.0f,
                0.0f,
                CGImageGetWidth(image.CGImage),
                CGImageGetHeight(image.CGImage));
        imageScale = self.frame.size.width/imageRect.size.width;

        NSLog(@"imageScale: %f",imageScale);
        imageRect.size = CGSizeMake(
                imageRect.size.width*imageScale,
                imageRect.size.height*imageScale);

        //根據圖片的縮放計算scrollview的縮放級別
        // 圖片相對於視圖放大了1/imageScale倍,所以用log2(1/imageScale)得出縮放次數,
        // 然後通過pow得出縮放倍數,至於為什麼要加1,
        // 是希望圖片在放大到原圖比例時,還可以繼續放大一次(即2倍),可以看的更清晰
         int level = ceil(log2(1/imageScale))+1;
        CGFloat zoomOutLevels = 1;
        CGFloat zoomInLevels = pow(2, level);

        self.maximumZoomScale =zoomInLevels;
        self.minimumZoomScale = zoomOutLevels;

        frontTiledView = [[TiledImageViewNoSlip alloc] initWithFrame:imageRect
                                                               image:image
                                                               scale:imageScale];
        [self addSubview:frontTiledView];
    }
    return self;
}
  其實在上面計算scrollview的放大倍數時,我有個疑問,如果放大的目的是讓屏幕可以顯示圖片正常的像素,那麼按照retina屏的特點,難道不應該讓這個倍數再除以屏幕的scale嗎?可是這樣一來圖片是達不到看到實際大小的效果的,這一點希望路過的大神不吝賜教~~   然後在scrollview的代理方法,提供縮放視圖:

 

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return frontTiledView;
}

  

 最後要出場的就是我們的tile layer了,也就是上面scrollview中創建的TiledImageViewNoSlip,其實它也只是個普通的UIView,只是重新指定了Layer為CATiledLayer,然後在tileLayer執行分塊繪製時(調用drawrect),根據給定的區域(rect)對應到超大圖的區域進行繪製,這個view的關鍵實現如下:

+ (Class)layerClass {
	return [CATiledLayer class];
}

-(id)initWithFrame:(CGRect)_frame image:(UIImage*)img scale:(CGFloat)scale {
    if ((self = [super initWithFrame:_frame])) {
		self.image = img;
        imageRect = CGRectMake(0.0f, 0.0f,
                CGImageGetWidth(image.CGImage),
                CGImageGetHeight(image.CGImage));
		imageScale = scale;
 		CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
         //根據圖片的縮放計算scrollview的縮放次數
        // 圖片相對於視圖放大了1/imageScale倍,所以用log2(1/imageScale)得出縮放次數,
        // 然後通過pow得出縮放倍數,至於為什麼要加1,
        // 是希望圖片在放大到原圖比例時,還可以繼續放大一次(即2倍),可以看的更清晰
       int lev = ceil(log2(1/scale))+1;
        tiledLayer.levelsOfDetail = 1;
        tiledLayer.levelsOfDetailBias = lev;
//        tiledLayer.tileSize  此處tilesize使用預設的256x256即可

    }
    return self;
}

-(void)drawRect:(CGRect)rect {
//將視圖frame映射到實際圖片的frame
    CGRect rec = CGRectMake(
            rect.origin.x / imageScale,
            rect.origin.y / imageScale,
            rect.size.width / imageScale,
            rect.size.height / imageScale
            );
//截取指定圖片區域,重繪
    CGImageRef cropImg = CGImageCreateWithImageInRect(self.image.CGImage, rec);
    UIImage *tileImg = [UIImage imageWithCGImage:cropImg];
    [tileImg drawInRect:rect];
}

  

ok, 到這裡就完成了一個簡單的超大圖展示功能,下麵繼續分析一下實現原理

實現原理分析

從簡單的開始,先不管LOD和LODB,使用預設值1, 0,直接看一下tileSize

tileSize

tile layer在繪製視圖層時,將View分割成最大不超過tileSize指定的尺寸,然後調用drawrect方法逐個區域繪製,註意這個“逐個”很重要,下麵會說為什麼 比如:視圖尺寸是 {375, 268}(也就是中國地圖縮放展示後的視圖尺寸),如果我們設置的尺寸超過375x268, 那麼整張圖會同時載入(註意這裡沒有設置LODB,如果設置的話結果就很樂觀了,LODB我們後面再討論),這樣和不使用tile layer的效果差不多,記憶體的峰值達到440M左右,如下圖:  

 

 使用預設的tile size256x256, 則視圖會劃分成4塊逐個載入,經過多次測試,記憶體峰值有所下降,為310M左右,畢竟這個尺寸相當於一次性繪製大部分的區域,如下圖:

 

設置tile size為{100, 100}, 多次測試記憶體峰值在110M左右

 

所以逐個載入的意義,就是分散同時繪製整個視圖的記憶體壓力,至此,tile size告一段落!

註:以上預設設置LOD和LODB,進行scrollview縮放時,layer不會進行重繪

接下來繼續探索~

 

levelsOfDetail

這個值表示layer在繪製時縮小層級設置,就是在縮小視圖時可以達到的最大縮小級數,可以和下麵的levelsOfDetailBias值對應起來,本次重點討論超大圖展示,主要涉及到放大,所以可以參考下麵的放大設置分析。

這個值是負責設定視圖縮小時的重繪節點, 通過實驗確實如此,在LODB預設值為0時, 無論設置LOD的值是多少,在放大圖片時,都不會進行重繪操作,視圖也就會越來越模糊,而對視圖進行縮小操作時,根據LOD的值不同,zoom scale達到觸發重繪的值會相應變化,LOD越大,zoom scale的觸發值就越小

既然LOD不影響放大的重繪結果,那就先置之不理吧,繼續看LODB~

 

levelsOfDetailBias

這個值表示layer在放大時,觸發重新繪製的最大層級設置。

簡單來說,就是從最小視圖需要放大多少次,才能達到我們需要的清晰度效果,註意是多少次,一次就是2倍,這個和scrollview的zoomScale不同,zoomscale表示的是放大多少倍~

數值越大,視圖在放大時可以重新渲染原圖的粒度就越細,在失真前視圖能呈現的紋理就越清晰,直到顯示到像素級別,這時tileSize已經無限接近0了,也就是每個tile負責繪製一個像素;

以上也說明瞭,繪製視圖時會分成多少個tile,除了與我們設置的tileSize有關,還與縮放級別、最大放大層級(也就是levelsOfDetailBias的值)有關。

tile的最大size為設置的tileSize尺寸,預設為256x256, 最小可以無限接近0,至於繪製時最小可以達到多少,就是levelsOfDetailBias說了算了,可以參考下圖數據表

說了這麼多,有點抽象,下表是我統計的 levelsOfDetailBias值不同時,隨著scrollview放大倍數的增加,tile可以達到的最小尺寸,省略了很多中間重繪的記錄,只寫出了每個tileSize最小時,縮放的倍數:

說明:

  • 375x268是圖片視圖的frame size
  • 中國地圖尺寸為:11935x8554
  • levelsOfDetail = 1(預設值,不影響放大)
  • tileSize = 256x256 預設值(為了不考慮tileSize對初始化和縮放的影響,此處也可以設置為375x268)
  • 圖中放大倍數值為 *, 表示縮放不再影響效果
  • 放大倍數的值為手動縮放的模糊值,不代表科學計算的實際值,只作為一個參考邊界
  • 隨著LODB的值變大,相同的tile size時,視圖能繪製的最大尺寸會更大
  • 值設置10以後,繼續放大圖片已經失真嚴重了,所以用灰色表示
  • 重點觀察tileSize的最小值變化

 

levelsOfDetailBias與最小tileSize、zoomScale關係 

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.慢sql情況查詢: 可以使用以下三種方式查詢,第一種是瞭解MySQL進程大概情況;第二種是按照影響時間倒序的,可以查詢到目前最慢的一條sql;第三種是防止sql 的info消息過長而無法顯示完整。 2.連接數的查詢: 可以使用以下sql查詢到當前實例下所有庫的連接數(由於該sql是根據同一個ho ...
  • 開發中常常會遇到因為數據存在多條導致查詢報錯的問題,下麵的方法一鍵解決此問題。 1.查詢重覆數據條數 select count(1) from tb_jf_order_drawcash where order_id in (select order_idfrom tb_jf_order_drawca ...
  • Android如何使用Https,這一篇文章是NoHttp系列中比較重要的,為大家介紹一下內容。 什麼是Https? HTTPS(全稱:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版 ...
  • 有沒有覺得Android的findViewById挺煩人的。使用Kotlin可以讓你徹底拋棄這個煩惱 步驟1、在build.gradle(Module:app)中添加如下一句話 這個在老一點版本的Android Studio中需要手動添加,我的是Android Studio3.0的,這句話是預設加上 ...
  • Android系統中,目前Dangerous級別的許可權都需要動態申請。步驟如下; 1、AndroidManfiest.xml中申明需要的動態許可權 2、代碼中檢查許可權、申請許可權 如下方法執行之後,會彈出提示框,提示要申請該許可權 3、獲取結果 轉載請註明鏈接:http://www.cnblogs.com ...
  • MvvmCross從4.0之後plugin的註冊介面做了重構,網上例子不多,這裡給個參考。本例子使用MvvmCross.Plugins.DownloadCache和MvvmCross.Plugins.File.PluginLoader來顯示網上的一個圖片。 1,View里先給個UIKit.UIIma ...
  • 在iOS開發中、經常用到圖片的本地化。 iOS 圖片本地存儲、本地獲取、本地刪除,可以通過以下類方法實現。 //將圖片保存到本地 + (void)SaveImageToLocal:(UIImage*)image Keys:(NSString*)key { //首先,需要獲取沙盒路徑 NSString ...
  • 內容摘要:Android Handler消息傳遞機制的學習總結、問題記錄 Handler消息傳遞機制的目的: 1.實現線程間通信(如:Android平臺只允許主線程(UI線程)修改Activity里的UI組件,而實際開發時會遇到新開的線程要改變界面組件屬性的情況,這時就要有一種辦法通知主線程更新UI ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...