cheez3d
cheez3d

Reputation: 61

Reading AND mask of 16x16 images from ICO file in Lua

I am creating a function that will parse and ICO/CUR and convert the data into plain pixels (specific to my API) that will then be fed to a dxCreateTexture function which will create the final image. I'm currently working on the case when the images inside the ICO file are 8bpp or less. Here's how it's currently done:

  1. I read the color palette and store each color inside an array.
  2. I move on to reading the XOR mask which contains the indices for every pixel color and store every pixel inside another table.
  3. I then read the AND mask which I understand is 1bpp.

The code that I will post below works perfectly for 1bpp, 4bpp and 8bpp images with a size of 32x32, XOR & AND masks being interpreted correctly, but for images with 8x8, 16x16 or 48x48 sizes (and I suspect that there are other sizes too) only the XOR mask gets interpreted correctly. Reading the AND mask will result in misplaced transparent pixels. Please keep in mind that I'm not flipping the image yet, so this code will result in an upside-down image.

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

I suppose that fileExists, fileOpen, fileClose, fileGetPos and fileSetPos are self-explanatory functions. The rest of the functions' arguments are as follows:

Here are some outputs of the function in its current state: https://i.sstatic.net/lLaid.png The first image is 16x16 with AND mask code commented out, second is 32x32 with AND mask code commented out, third is 16x16 with AND mask code and fourth is 32x32 with AND mask code. 8x8 and 48x48 images with AND mask code look the same as the third image in the demonstration.

ICO used for demonstration: http://lua-users.org/files/wiki_insecure/lua-std.ico

Upvotes: 4

Views: 617

Answers (1)

cheez3d
cheez3d

Reputation: 61

Thank you, @EgorSkriptunoff!

This mask is also a subject to right-padding its every line with zeroes.

This was indeed the problem. Three lines of code inside each loop solved it.

XOR mask:

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

AND mask:

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

Upvotes: 2

Related Questions