崩壞學園2及大部分采用ETC1壓縮格式的Unity3D游戲的拆包圖處理


0x0 背景

眾所周知(?) , 大部分(?)以Unity3D為引擎的手游為了進一步壓縮資源大小, 在Android平台經常將貼圖資源以ETC1格式壓縮以減少體積. 蛋疼的是ETC1不支持Alpha通道.... 

程序猿們選擇將原圖拆分, 用一張貼圖來單獨記錄Alpha信息. 這就給后來的拆(偷)包(圖)帶來了不便(不要臉(*≧▽≦)), 怎么辦呢? 合並回去就行了呀!(理直氣壯)

0x1 實現

眾...... 一張RGBA圖片包含三個顏色通道以及一個Alpha通道, 經過拆分之后就變成了一張圖片記錄原圖的RGB參數, 一張圖片僅記錄Alpha參數.

如崩崩的拆包圖: 

那么要想合並回去, 只需要在一圖中獲取RGB對應的值, 在二圖中獲取Alpha的值, 然后合並在一起生成一一張RGBA信息的圖保存下來.

本文所用語言為C#, 首先用易於理解的GetPixel()方法寫一次~

 

0x2 核心代碼 - 獲取像素法

 1 private Bitmap mergeImageOld(Bitmap rgbTexture,Bitmap alphaTexture)
 2 {
 3     textureWithAlpha = new Bitmap(rgbTexture.Width, rgbTexture.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); //新建一個與RGB同分辨率的Bitmap
 4     try
 5     {
 6         for (int i = 0; i < rgbTexture.Width; i++)
 7         {
 8             for (int j = 0; j < rgbTexture.Height; j++)
 9             {
10                 Color withAlpha = Color.FromArgb(alphaTexture.GetPixel(i, j).R, rgbTexture.GetPixel(i, j));
11                 textureWithAlpha.SetPixel(i, j, withAlpha);
12             }   //internal for end
13         }   //for end
14 
15         return textureWithAlpha;
16     }
17     catch(Exception ex)
18     {
19         Console.WriteLine(ex.Message);
20         return textureWithAlpha;
21     }
22 }   //mergeImageOld()

其中6-13行是關鍵代碼 for循環中逐行逐像素處理 

這里第十行用到的重載為:

Color Color.FromArgb(int alpha,Color baseColor);

第一個參數就是Alpha值, 第二個參數為顏色, alphaTexture.GetPixel(i, j).R 的意思的是在alphaTexture中第i行第j個像素獲取紅色分量, rgbTexture.GetPixel(i, j) 的意思是在rgbTexture中獲取顏色

Alpha值拿到了, 顏色也拿到了, 直接SetPixel()就好咯~

 

0x3 進階代碼 - 指針法

上面的代碼跑起來有個最大的問題就是..........太雞兒慢了........本身GetPixel()就慢如蝸牛  我們還要在兩張圖中GetPixel()..... 

筆者的電腦上處理一張1.21M 1024*1024大小的圖片耗時2.4秒左右 這怎么能受得了 

翻閱前輩資料后決定使用指針法來代替獲取像素 

使用指針必須在項目設置中勾選允許不安全的代碼  並且涉及到指針操作的代碼必須放在  unsafe {.....} 區域中  不然不讓編譯~

代碼如下: 

 1 public unsafe Bitmap mergeImage(Bitmap rgbTexture,Bitmap alphaTexture)
 2 {
 3     int width = rgbTexture.Width;
 4     int height = rgbTexture.Height;
 5 
 6     try
 7     {
 8         BitmapData textureWithAlphaData = rgbTexture.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);    //將圖像鎖定到內存中以便操作
 9         BitmapData alphaTextureData = alphaTexture.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
10 
11         byte* resultP = (byte*)textureWithAlphaData.Scan0;  //獲取在內存的中首地址
12         byte* alphaP = (byte*)alphaTextureData.Scan0;
13 
14         for (int j = 0; j < height; j++)
15         {
16             for (int i = 0; i < width; i++)
17             {
18                 resultP[3] = alphaP[2]; //ARBG在內存中存儲順序為GBRA 所以resultP[3]即為Alpha分量  resultP[2]即為紅色分量 
19                 resultP += 4;   //下移4個位置 處理下一個像素的信息
20                 alphaP += 4;
21             }
22         }
23 
24         rgbTexture.UnlockBits(textureWithAlphaData);    //解鎖
25         alphaTexture.UnlockBits(alphaTextureData);
26 
27         return rgbTexture;
28     }
29     catch
30     {
31         return rgbTexture;
32     }
33 }   //mergeImage()

 

其中第8行要注意第二個參數設置成讀寫或只寫(ImageLockMode.WriteOnly), 第三個參數因為這里需要帶透明通道的ARGB格式所以設置成32位AGRB   

第9行就可以設置成只讀了(ImageLockMode.ReadOnly) 而且因為本身讀的圖片就不帶Alpha通道 也可以將格式設置成 PixelFormat.Format32bppRgb

第18行注意內存中32位ARGB格式Bitmap的次序為[G,B,R,A] 所以Alpha分量其實在第四個位置

另外一定要注意釋放資源  筆者放在了這個方法外面 不然處理數量一多分分鍾內存爆炸

1 rgbTexture.Dispose();
2 alphaTexture.Dispose();
3 textureWithAlpha.Dispose();

0x4 指針法補充

本文的例子由於需要Alpha通道, 所以直接采用了32位ARGB格式, 32位的Bitmap中有每像素占用四個字節, 每行數據的長度必定為4的倍數,所以不用考慮對齊

而另外也很常見的24位圖每個像素占用的字節數就為24/8 = 3, 這時候每行的數據長度就不一定為4的倍數了 

舉個栗子: 一張 10 * 10 的24位圖片  每一行的數據長度為 3 * 10 = 30 字節  這時就會自動用"0"補充到32字節, 一共補充了 32 - 30 = 2個字節

那么如果在這種情況下使用指針來讀取數據, 就必須跳過這些補位的字節  每行的實際字節數的獲取方法為 BitmapData類中的屬性 BitmapData.Stride

所以要處理24位的圖片時, 需要在第一層for循環結尾處(即每行結尾處跳過占位的字節  如上面那個栗子就要跳2個字節)

代碼如下 可以跟0x3中的對比一下

 

 1 byte* resultP = (byte*)textureWithAlphaData.Scan0;  //獲取在內存的中首地址
 2 byte* alphaP = (byte*)alphaTextureData.Scan0;
 3 
 4 int resultOffset = textureWithAlphaData.Stride - width * 3; //用實際占位過的長度來減去圖片每行有效像素占用的長度  此行代碼僅對24位圖有效
 5 int alphaOffset = textureWithAlphaData.Stride - width * 3;
 6 
 7 for (int j = 0; j < height; j++)
 8 {
 9     for (int i = 0; i < width; i++)
10     {
11         resultP[2] = alphaP[2]; //RBG在內存中存儲順序為GBR  這里僅僅舉栗子方便對比  這行代碼跑過之后會將2圖的紅色分量設置到1圖中去
12         resultP += 3;   //下移3個位置 處理下一個像素的信息
13         alphaP += 3;
14     }
15     resultP += resultOffset;    //由於上面用的是圖片的寬度  所以現在指針停在了占位字節的前面  所以這里需要跳過多出的字節
16     alphaP += alphaOffset;
17 }

 

 

 

0x5 結尾

代碼地址: https://github.com/yyuueexxiinngg/HSoD2TextureMerge

用到的提取工具: https://github.com/Perfare/UnityStudio

兩種方式的耗時對比如圖:   (右鍵新標簽打開)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM