Delphi - Több ikon elhelyezése egyetlen ICO állományban

forráskód letöltése
Egy ICO állomány egyszerre több képet is tartalmazhat. A képek mérete és színmélysége eltérő is lehet. Készítünk egy komponenst, amely segítségével egy megadott könyvtár összes ikonját egyesíthetjük, egyetlen ICO állománnyá.
A mellékelt példaprogram megnyitása előtt a CatIcons.pas-ban lévő komponenst telepítenie kell a Delphi alá. Ehhez válassza a Component - Install Component menüpontot.
A Directory property-ben kell megadnunk annak a könyvtárnak a nevét, amelyből az ikonokat egyesíteni akarjuk. A FileName property-ben kell megadnunk a létrejövő állomány nevét, amelybe az ikonokat el akarjuk helyezni. A megadott könyvtár állományainak feldolgozása az Execute metódus meghívásával kezdődik.
Az ICO állomány szerkezetében a legelső helyen egy fejléc található, amely meghatározza az állományban található képek számát.
TIconHeader = record
  idReserved: Word;
  idType: Word;
  idCount: Word;
end;
Az idReserved értéke mindig 0, az idType értéke pedig mindig 1. Az állományban található ikonok számát az idCount mezőből olvashatjuk ki.
A fejlécet a TIconEntry struktúra követi. A struktúrából annyi példány következik egymás után, ahány kép található az ICO állományban. Ez a struktúra tulajdonképpen a kép fejlécének is tekinthető. Mezőiből megtudhatjuk a kép méretét képpontokban és bájtokban, valamint magának a képadatnak az elhelyezkedését az állományon belül.
TIconEntry = record
  bWidth: Byte;
  bHeight: Byte;
  bColorCount: Byte;
  bReserved: Byte;
  wPlanes: Word;
  wBitCount: Word;
  dwBytesInRes: Longint;
  dwImageOffset: Longint;
end;
A kép szélességét és magasságát, képpontokban mérve, a bWidth és bHeight mezőkből tudhatjuk meg. Az bColorCount értéke adja meg a képen használt színek számát. A bReserved, wPlanes, és wBitCount mezők értéke nem használt. A feldolgozáshoz nélkülözhetetlen két adat, a kép mérete bájtokban, valamint a képet tartalmazó első bájt helye az állományon belül. Ezeket a dwBytesInRes és dwImageOffset mezőkből olvashatjuk ki.
A fenti információk elegendőek arra, hogy az állományon belül meghatározzuk a keresett kép helyét, és azt kimásoljuk onnan. Az állományban található képek DIB formátumban vannak tárolva.
A létrejövő ikon állomány fejlécében meg kell adnunk a benne elhelyezendő képek számát. Az állomány fejlécét követik a képek fejlécei, és csak az állomány utolsó blokkjában helyezkednek el maguk a képek.
Mivel először a fejléceket kell elhelyeznünk, szükséges egy elő feldolgozást végeznünk, amely során megtudjuk a szükséges adatokat. Az alábbi adatokat kell feljegyeznünk.
TFilesData = record
  dFileName: String;
  dIndexInFile: LongInt;
  dIconEntry: TIconEntry;
end;
A tulajdonságok tárolására a FilesData, dinamikus tömböt használjuk.
A dFileName-be jegyezzük fel az állomány nevét, a dIndexInFile-ban a kép sorszámát az adott állományon belül. A sorszámozást itt 1-el kezdjük. A dIconEntry mezőbe az adott kép fejléc adatai kerülnek. Ebből a fejlécből tudjuk meghatározni a kép méretét bájtokban, valamint a mező dwImageOffset értékében kell megadnunk majd a kép helyét az új állományban.
A megadott könyvtárban lévő ICO állományok megkeresésére a FindFirst – FindNext párost használjuk.
Egy megtalált ICO állomány elő feldolgozását a SearchForIcons metódus végzi el.
procedure SearchForIcons(ICOFileName: String);
A metódusnak, az ICO állomány elérési útját és nevét kell megadni.
Az ICO állományt egy TMemoryStream típusú változó segítségével nyitjuk meg. Először beolvassuk a fejlécét, amelyből kiderül, hogy hány képet tartalmaz.
Read(IconHeader,SizeOf(TIconHeader));
Ez alapján be tudjuk olvasni a képek fejléceit, egy for ciklus segítségével.
for i:=1 to IconHeader.idCount do begin
  Read(IconEntry,SizeOf(TIconEntry));
A fejlécet és az adatokat feljegyezzük a FilesData tömbben.
Az ikonok egyesítéséhez szükségünk van egy újabb eljárásra.
procedureGetImageData(FileName: String; Index: Integer; var ImageData: TMemoryStream);
A függvénynek meg kell adnunk az ICO állomány nevét és azon belül a kép indexét. Az ImageData paraméterben visszakapjuk magát a képet, TMemoryStream formájában.
A függvény működése csak abban különbözik a SearchForIcons függvénytől, hogy a fejléceken túl, itt a képadatot is ki kell olvasnunk az állományból.
Seek(IconEntry.dwImageOffset,soFromBeginning);
ImageData.SetSize(IconEntry.dwBytesInRes);
ImageData.CopyFrom(Stream,IconEntry.dwBytesInRes);
Ezután minden adat a rendelkezésünkre áll ahhoz, hogy létrehozzuk az új állományt, amely az összes ikont tartalmazni fogja.
Az új állományt egy TFileStream segítségével hozzuk létre.
FileStream:=TFileStream.Create(FFileName,fmCreate);
A CurrImageOffset változóban elmentjük a legelső kép leendő helyét az állományban.
CurrImageOffset:=SizeOf(TIconHeader)+Length(FilesData)*SizeOf(TIconEntry);
Az ICO állomány fejlécébe be kell állítanunk az elhelyezendő képek számát.
Az egyes képek fejlécében meg kell adnunk a kép elhelyezkedését.
FilesData[i].dIconEntry.dwImageOffset:=CurrImageOffset;
A fejléc beírása után a CurrImageOffset értékét növelnünk kell az aktuális kép méretével.
CurrImageOffset:=CurrImageOffset+FilesData[i].dIconEntry.dwBytesInRes;
A fejlécek beírása után jönnek a képek a GetImageData függvény segítségével.
GetImageData(FilesData[i].dFileName,FilesData[i].dIndexInFile-1,ImageData);
ImageData.Seek(0,soFromBeginning);
if ImageData<>Nil then
  FileStream.CopyFrom(ImageData,FilesData[i].dIconEntry.dwBytesInRes);