1 壓縮原理
要明白 USI 的壓縮原理,首先需要對圖像的存儲方法有一個基礎的懂得。USI 壓縮是建立在索引色的基礎上進行的。
1.1 索引圖與RGB圖
對於PNG圖像,可以分為索引(Index)圖和RGB圖兩種,索引圖只包含固定數目標色彩,而RGB圖的色彩數目是不受限制的。
RGB圖的每一個象素都保留一個RGB值,代表這個象素的色彩,因此,一張RGB圖有多少個象素,文件中就保留多少個RGB值。
而索引圖會將其固定數目標色彩,按照次序排列起來,作為色彩的索引保留在文件頭中,被稱為調色板(palette)。每一個象素只保留其色彩在調色板中的索引。
如一個32色的索引圖,在文件頭中保留了32個色彩,索引值從0到31。圖中每一個象素只記錄其色彩的索引。
因此,對於一般的PNG圖,索引圖文件的大小總是小於RGB圖的。
1.2 行程壓縮原理
當我們把一張索引圖的所有象素(N個),按照從上到下,從左至右,即按行掃描的次序排列起來的時候,我們得到一個隊列。假如我們用1個字節來存儲一個象素
的索引值(調色板色彩不超過256),那麼數據的大小為N字節。這段數據的格局我們表現為 [I1][I2]…[In] 共 N 個。
在上面的隊列中,可能會呈現許多持續雷同的索引值,最多的就是透明色。假如我們在每個索引值前用1個字節保留這個值持續呈現的數目(最多可以表現256個
),那數據的格局變為 [C1][I1][C2][I2]…[Cm][Im] 共 M 個。那麼一張256個象素的單色圖的所有數據,只需要2個字節來保留。通常,我們所需的圖中總
是有***持續的色彩,包含透明色,因此按照這個格局保留的圖像,其文件大小可以大大下降,這就是行程的壓縮原理。
1.3 USI壓縮原理
假如一張索引圖的色彩數為32,那麼在[C1][I1][C2][I2]…[Cm][Im] 格局中,I的數值都小於32,那麼每個字節前3 bits 始終為0。為了充分利用這 3
bits,我們可以將 C 的值保留在這 3bits中,這樣我們的格局變為 [G1][G2]….[Gk] 共 K 個(G的高位為數目,低位為色彩索引)。這樣,對於32色的圖,
每個字節最多可以保留8個象素的信息,對於64色的圖,每個字節最多可以保留4個象素的信息,對於16色的圖,每個字節最多可以保留16個象素的信息。
在 [G1][G2]….[Gk] 這K個字節前,再加上調色板數據和其它本圖的必要信息,就得到了USI格局的文件。
conan(29842977) 15:03:01
1.1 載進文件
private void load(String file) {
try {
DataInputStream din = new DataInputStream(getClass().getResourceAsStream(file));
m_flags = din.readInt(); //格局標記
/** 讀取調色板信息 */
m_count = din.readByte() & 0xff; //調色板位數
m_mask = 0xff >> (8 - m_count); //盤算 取色板索引的掩碼
int pal_count = din.readByte() & 0xff; //調色板數目
int pal_len = din.readByte() & 0xff; //調色板長度 即色彩數
m_pal = new int[pal_count][pal_len]; //初始化調色板容器
int pal;
//讀取調色板信息
for (int i = 0; i < pal_count; i++) {
for (int j = 0; j < pal_len; j++) {
pal = din.readShort() & 0xffff;
m_pal[i][j] = (
( ( ( (pal & 0xF000) >>> 12) * (17 << 24)) & 0xFF000000) |
( ( ( (pal & 0x0F00) >>> 8) * (17 << 16)) & 0x00FF0000) |
( ( ( (pal & 0x00F0) >>> 4) * (17 << 8)) & 0x0000FF00) |
( ( ( (pal & 0x000F) * 17)))
);
}
}
/** 讀取圖塊信息 */
m_modelCount = din.readShort() & 0xffff; //圖塊數目
//讀取圖塊尺寸
if ( (m_flags & FLAG_REBUILD_SIZE) != 0) {
//基於尺寸的轉換方法
m_rebuildWidth = din.readByte() & 0xff;
m_rebuildHeight = din.readByte() & 0xff;
} else if ( (m_flags & FLAG_REBUILD_MODULE) != 0) {
//基於動畫model的轉換方法
m_models = new byte[m_modelCount * 2];
din.read(m_models);
}
/** 讀取像素數據 */
m_dataSize = din.readInt(); //像素數據大小(壓縮數據)
m_data = new byte[m_dataSize];
din.read(m_data); //讀取像素數據(壓縮數據)
//讀取每個圖塊數據的起始偏移量
int offset = 0;
m_dataOffset = new int[m_modelCount];
for (int i = 0; i < m_modelCount; i++) {
m_dataOffset[i] = offset;
if ( (m_flags & FLAG_16BIT_4_LEN) != 0) {
offset += din.readShort();
} else {
offset += din.readByte() & 0xff;
}
}
} catch (Exception ex) {}
}
1.2 解壓縮
/******************************************
* 解壓縮指定圖塊像素數據
* @param model_id int 圖塊號
* @param pal_id int 調色板號
* @return int[] 解壓縮圖塊像素數據(ARPG值)
******************************************/
private int[] BuildRle8bFrm(int model_id, int pal_id) {
//盤算解壓後,像素數據的大小(圖塊W*圖塊H)
int size;
if ( (m_flags & FLAG_REBUILD_SIZE) != 0) {
size = m_rebuildWidth * m_rebuildHeight;
} else {
size = (m_models[model_id * 2] & 0xff) *
(m_models[model_id * 2 + 1] & 0xff);
}
//初始化像素buf
int[] m_bufB = new int[size];
int pal[] = m_pal[pal_id]; //獲取當前調色板
int offset = m_dataOffset[model_id]; //獲取壓縮數據出發點
//解壓縮
int count, index, pos = 0;
while (pos < size) {
count = ( (m_data[offset] & 0xFF) >> m_count) + 1;
index = pal[m_data[offset] & m_mask];
offset++;
while (--count >= 0) {
m_bufB[pos++] = index;
}
}
return m_bufB;
}
/**********************************
* 獲取指定圖塊Image
* @param model_id int 圖塊號
* @param pal_id int 調色板號
* @return Image 圖塊Image對象
**********************************/
public Image GetImage(int model_id, int pal_id) {
//獲得指定圖塊解壓數據(ARPG色彩數據)
int[] m_bufB = BuildRle8bFrm(model_id, pal_id);
//盤算圖塊尺寸
int w, h;
if ( (m_flags & FLAG_REBUILD_SIZE) != 0) {
w = m_rebuildWidth;
h = m_rebuildHeight;
} else {
w = m_models[model_id * 2] & 0xff;
h = m_models[model_id * 2 + 1] & 0xff;
}
//天生Image圖片
Image m_image = Image.createRGBImage(m_bufB, w, h, true);
m_bufB = null;
return m_image;
}