2
votes

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:

  • fileRead(file file, number bytes) - reads bytes bytes from file and returns them as a string
  • fileReadInteger(file file, [number bytes = 4], [bool order = true]) - reads an integer with the size of bytes bytes from file in order (little -edian = true, big-edian = false)
  • bitExtract(number value, number filed, number width)
  • dxCreateTexture(string pixels, [string format = "argb"], [bool mipmaps = true])

Here are some outputs of the function in its current state: http://i.imgur.com/dRlaoan.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

1
The matrix of pixels stored in BMP/CUR file does not look like a packed solid array. It has gaps after every row of pixels. That is, every line of the image is right-padded with zeroes so its size is multiple of 4 bytes. These gaps are absent for 32x32 images because image width (in bytes) is multiple of 4.Egor Skriptunoff
I'm not sure if I understood correctly. For example, for a 32x32 4bpp BMP the row width would be SizeX/PixelsPerByte == 32/2 == 16 == 4*4 (multiple of 4). For a 16x16 4bpp it would be 16/2 == 8 == 4*2 (also multiple of 4). There is something silly I'm missing.cheez3d
If I understood correctly, in 16x16 4bpp image AND-mask is stored as 1 bpp, isn't it? This mask is also a subject to right-padding its every line with zeroes.Egor Skriptunoff
Thank you! This was indeed the problem :).cheez3d

1 Answers

1
votes

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