Lua中从ICO文件读取16x16图像的AND掩码

4
我正在创建一个函数,用于解析ICO/CUR并将数据转换为纯像素(特定于我的API),然后将其馈送到dxCreateTexture函数中,该函数将创建最终的图像。目前,我正在处理ICO文件内部图像为8bpp或更低情况的情况。以下是当前的处理方式:
  1. 读取颜色调色板并将每种颜色存储在数组中。
  2. 继续读取XOR掩码,其中包含每个像素颜色的索引,并将每个像素存储在另一个表中。
  3. 然后读取AND掩码,我理解它是1bpp。
下面是代码,对于大小为32x32的1bpp、4bpp和8bpp图像,XOR和AND掩码都能被正确解释,但对于大小为8x8、16x16或48x48的图像(我怀疑还有其他大小),只有XOR掩码能被正确解释。读取AND掩码会导致透明像素错位。请注意,我尚未翻转图像,因此此代码将生成一个倒置的图像。
local IcoSignature = string.char(0,0,1,0);
local PngSignature = string.char(137,80,78,71,13,10,26,10);

local AlphaByte = string.char(255);
local TransparentPixel = string.char(0,0,0,0);

function ParseCur(FilePath)
    if (fileExists(FilePath) == true) then
        local File = fileOpen(FilePath);
        if (File ~= false) and (fileRead(File,4) == IcoSignature) then
            local Icons = {}
            for i = 1,fileReadInteger(File,2) do -- number of icons in file
                local SizeX = fileReadInteger(File,1); -- icon width
                if (SizeX == 0) then
                    SizeX = 256;
                end
                local SizeY = fileReadInteger(File,1); -- icon height
                if (SizeY == 0) then
                    SizeY = 256;
                end

                fileRead(File,2); -- skip ColorCount and Reserved

                local PlanesNumber = fileReadInteger(File,2);
                local BitsPerPixel = fileReadInteger(File,2);

                local Size = fileReadInteger(File); -- bytes occupied by icon
                local Offset = fileReadInteger(File); -- icon data offset

                Icons[i] = {
                    PlanesNumber = PlanesNumber,
                    BitsPerPixel = BitsPerPixel,

                    SizeX = SizeX,
                    SizeY = SizeY,

                    Texture = true
                }

                local PreviousPosition = fileGetPos(File);

                fileSetPos(File,Offset);
                if (fileRead(File,8) == PngSignature) then -- check data format (png or bmp)
                    fileSetPos(File,Offset);

                    -- to do
                else
                    fileSetPos(File,Offset+4); -- skip BITMAPINFOHEADER Size

                    local SizeX = fileReadInteger(File);
                    local SizeY = fileReadInteger(File)/2;

                    local PlanesNumber = fileReadInteger(File,2);
                    local BitsPerPixel = fileReadInteger(File,2);

                    fileRead(File,24); -- skip rest of BITMAPINFOHEADER

                    local Pixels = {}
                    if (BitsPerPixel == 1) or (BitsPerPixel == 4) or (BitsPerPixel == 8) then
                        local Colors = {}
                        for j = 1,2^(PlanesNumber*BitsPerPixel) do
                            Colors[j] = fileRead(File,3)..AlphaByte;
                            fileRead(File,1);
                        end

                        local PixelsPerByte = 8/BitsPerPixel;
                        local CurrentByte;

                        for y = 1,SizeY do -- XOR mask
                            Pixels[y] = {}

                            local CurrentRow = Pixels[y];
                            for x = 0,SizeX-1 do
                                local CurrentBit = x%PixelsPerByte;
                                if (CurrentBit == 0) then
                                    CurrentByte = fileReadInteger(File,1);
                                end

                                CurrentRow[x+1] = Colors[bitExtract(
                                    CurrentByte,
                                    (PixelsPerByte-1-CurrentBit)*BitsPerPixel,BitsPerPixel
                                )+1];
                            end
                        end

                        for y = 1,SizeY do -- AND mask
                            local CurrentRow = Pixels[y];
                            for x = 0,SizeX-1 do
                                local CurrentBit = x%8;
                                if (CurrentBit == 0) then
                                    CurrentByte = fileReadInteger(File,1);
                                end

                                if (bitExtract(CurrentByte,7-CurrentBit,1) == 1) then
                                    CurrentRow[x+1] = TransparentPixel;
                                end
                            end
                        end

                        for y = 1,SizeY do -- concatenate rows into strings
                            Pixels[y] = table.concat(Pixels[y]);
                        end


                        Icons[i].Texture = dxCreateTexture(
                            table.concat(Pixels)..string.char(
                                bitExtract(SizeX,0,8),bitExtract(SizeX,8,8),
                                bitExtract(SizeY,0,8),bitExtract(SizeY,8,8)
                            ), -- plain pixels
                            nil,
                            false
                        );
                    elseif (BitsPerPixel == 16) or (BitsPerPixel == 24) or (BitsPerPixel == 32) then
                        -- to do
                    end
                end

                fileSetPos(File,PreviousPosition); -- continue reading next ICO header
            end
            fileClose(File);

            return Icons;
        end
    end
end

我认为fileExists、fileOpen、fileClose、fileGetPos和fileSetPos这些函数都很容易理解。其余函数的参数如下:

  • fileRead(file 文件, number 字节数) - 从文件中读取字节数个字节并将其作为字符串返回。
  • fileReadInteger(file 文件, [number 字节数 = 4], [bool 顺序 = true]) - 以顺序(小端序=true,大端序=false)从文件中读取大小为字节数的整数。
  • bitExtract(number , number 位域, number 宽度)
  • dxCreateTexture(string 像素, [string 格式 = "argb"], [bool mipmaps = true])

这是函数当前状态下的一些输出:http://i.imgur.com/dRlaoan.png第一张图片是16x16,注释掉了AND mask code;第二张是32x32,注释掉了AND mask code;第三张是16x16,并且注释掉了AND mask code;第四张是32x32,并且注释掉了AND mask code。8x8和48x48的图片与演示中的第三张图片相同。

用于演示的ICO:http://lua-users.org/files/wiki_insecure/lua-std.ico


2
BMP/CUR文件中存储的像素矩阵看起来不像是一个紧密打包的数组。每行像素后面都有间隙,也就是说,图像的每一行都会用零进行右填充,以使其大小为4个字节的倍数。对于32x32的图像,这些间隙是不存在的,因为图像宽度(以字节为单位)是4的倍数。 - Egor Skriptunoff
我不确定我是否理解正确。例如,对于一个32x32 4bpp的BMP,行宽度将是SizeX/PixelsPerByte == 32/2 == 16 == 44(4的倍数)。对于一个16x16 4bpp,它将是16/2 == 8 == 42(也是4的倍数)。有一些愚蠢的事情我没考虑到。 - cheez3d
1
如果我理解正确的话,在16x16 4bpp图像中,AND掩码存储为1bpp,是吗?此掩码的每行也需要使用零进行右填充。 - Egor Skriptunoff
1
谢谢!这确实是问题的原因 :)。 - cheez3d
1个回答

2

感谢,@EgorSkriptunoff!

这个掩码的每一行也需要用零进行右填充。

这确实是问题所在。在每个循环内添加三行代码解决了这个问题。

XOR 掩码:

if ((SizeX/PixelsPerByte)%4 ~= 0) then
    fileRead(File,4-SizeX/PixelsPerByte%4);
end

AND掩码:

if ((SizeX/8)%4 ~= 0) then
    fileRead(File,4-SizeX/8%4);
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接