Decode ICO file manually

Started by
7 comments, last by BlackJoker 4 years, 11 months ago

Hi to all.

Currently I am trying to decode ICO file with my own loader, but I am getting weird results.

Here is screen from debugger.

image.png.213d34e300a254815e7cb3f56dc992d0.png

I have icon 170*170 px, with 32bit color, but in header info I see that height is twice bigger. It will be ok if image size also correspond to the size, but It does not.

170*170*4 = 115 600

170*340*4 = 231 200

But image size is 119 680.

After I read 115 600, there are 4080 bytes array, which is mostly filled with zeroes.

If I understood correctly, this is transparency buffer. I applied it, but in any case colors does not change.

Here is how I read colors data:


int realBitsCount = (int)iconImageInfo.Header.bitCount;
bool hasAndMask = /*(realBitsCount < 32) &&*/ (height != iconImageInfo.Header.height);

dataPtr = IntPtr.Add(dataPtr, Marshal.SizeOf<IconImageInfo>());
var buffer = new byte[width * height * 4];

var stream = new UnmanagedMemoryStream((byte*)dataPtr, iconInfo.BytesInRes);
var sizeinBytes = realBitsCount / 8;
int bufferOffset = 0;
if (realBitsCount == 32)
{
  for (int i = 0; i < buffer.Length; i += sizeinBytes)
  {
    stream.Read(buffer, bufferOffset, sizeinBytes);
    Utilities.Swap(ref buffer[bufferOffset], ref buffer[bufferOffset + 2]);
    bufferOffset += sizeinBytes;
  }
}

and here is how I read and apply transparency:


int shift;
int shift2;
byte bit;
int mask;

int boundary = width * realBitsCount; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico)
while (boundary % 32 != 0) boundary++;

boundary = width;
while (boundary % 32 != 0) boundary++;

var bufSize = iconInfo.BytesInRes - (width * height * (realBitsCount / 8));
var transparencyBuffer = new byte[bufSize];
stream.Read(transparencyBuffer, 0, (int)bufSize);
for (int y = 0; y < height; y++)
{
  for (int x = 0; x < width; x++)
  {
    shift = 4 * (x + y * width) + 3;
    bit = (byte)(7 - (x % 8));
    shift2 = (x + (height - y - 1) * boundary) / 8;
    var b = transparencyBuffer[shift2];
    mask = (0x01 & (b >> bit));
    var before = buffer[shift];
    buffer[shift] *= (byte)(1 - mask);
  }
}

And here is what I receive and what I should receive (from left to right)

image.png.1ed7c61e70f3beae5b66adaf54b992bc.pngimage.png.5048cf7190df77796b09dee2cd0fb250.png      

Does anyone know what I am doing wrong here? Please, help me.

 

Advertisement

It looks like you have your RGBA order mixed up.  If I recompose the channels from your final image, I get the source image.  Your final image seemed to be ordered more like GARB instead of RGBA.  When I recompose, I selected:

R->G
G->A
B->R
A(nothing)->B
 

rgba-compose.png

@xycsoscyx

Very interesting. I thought that colors inside ico is in BGRA format.... Its very very strange. Need to check. Thanks for aimingю

I tried with another ico and found that there is also RGB, AND and XOR masks (There are no such data for the icon above). Color actually not correct, but seems these color should be changed by this masks somehow. Does anyone know how to correctly apply this masks?

image.png.aa3997961a2d82f1d321d64017dc5ed3.png

@xycsoscyx

Regarding channels - I tried various combinations, but none of them give me at least nearly the same colors as in the original ico file. 

BTW, one more interesting things I found: If Ico file has 24 bit color depth, colors are good, but if it has 32 bit -> colors are messed up.

Simply: you are using the wrong data types, and have other bugs in your parser.  You don't show enough code, but there are several bugs clearly visible.

The screenshot shows a height of 340.  The ICO file format uses a BYTE to store the height.  Any value greater than 255 is a bug.

Specifically it contains this structure for each icon in the file:


 typedef struct tagICONDIRENTRY      
 {      
     BYTE bWidth;      
     BYTE bHeight;      // <<---- Cannot be greater than 255 in an ICO file.
     BYTE bColorCount;      
     BYTE bReserved;      
     WORD wPlanes;      
     WORD wBitCount;      
     DWORD dwBytesInRes;      
     DWORD dwImageOffset;      
 }ICONDIRENTRY, *LPICONDIRENTRY;   

 

We can't know that without knowing your actual code, but I note from your screen shot, you don't even know the actual data types because you are using var types that are deduced from context.  Maybe they're the right ones, maybe they're not. Based on what came up in the hover, it probably isn't the right type.

Based on what fields are showing, it looks like you've got a bitmap header rather than an icon header. If your code attempted to read the wrong structure from the file, of course it will not match the data in the file because it doesn't match what the file actually uses.

When working with file formats like this, you cannot take the lazy approach of deduced implicit types.  Working with file formats you must either use a byte array directly or explicitly use the correct struct which is properly packed and ordered. Using var for this sort of thing leads to bugs when you force the compiler to guess your intention.

@frob

Actually you are right. I found out that last structure I am reading should be 40 bytes instead of 46 bytes. So, this 6 bytes were producing all there errors. Seems now ico files reading correctly.

This topic is closed to new replies.

Advertisement