lazarus/lcl/include/icon.inc

1443 lines
38 KiB
PHP

{%MainUnit ../graphics.pp}
{******************************************************************************
TCustomIcon
******************************************************************************
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
function IconCompare(Item1, Item2: Pointer): Integer;
var
Icon1: TIconImage absolute Item1;
Icon2: TIconImage absolute Item2;
begin
Result := CompareValue(Icon1.Width, Icon2.Width);
if Result=0 then
Result := -CompareValue(Ord(Icon1.PixelFormat), Ord(Icon2.PixelFormat));
end;
const
IconSignature: array [0..3] of Byte = (0, 0, 1, 0);
type
TIconHeader = {packed} record // packed it not needed
idReserved: Word; // 0
idType: Word; // 1 - Icon, 2 - Cursor
idCount: Word; // number of icons in file
end;
TIconDirEntry = {packed} record // packing not needed
bWidth: Byte; // a value of 0 means 256
bHeight: Byte; // a value of 0 means 256
bColorCount: Byte; // number of entires in pallette table below
bReserved: Byte; // not used = 0
case Byte of
1: (
// icon
wPlanes: Word; // number of planes, should be 0 or 1
wBpp: Word; // bits per pixel
// common
dwBytesInRes: Longint; // total number bytes in images including pallette
// data: XOR, AND and bitmap info header
dwImageOffset: Longint; // pos of image as offset from the beginning of file
);
2:(
// cursor
wXHotSpot: Word;
wYHotSpot: Word;
);
end;
PIconDirEntry = ^TIconDirEntry;
// executables and libraries has the next structures for icons and cursors
PGrpIconDirEntry = ^TGrpIconDirEntry;
TGrpIconDirEntry = packed record
bWidth: Byte; // Width, in pixels, of the image
bHeight: Byte; // Height, in pixels, of the image
bColorCount: Byte; // Number of colors in image (0 if >=8bpp)
bReserved: Byte; // Reserved
wPlanes: Word; // color planes
wBpp: Word; // bits per pixel
dwBytesInRes: Dword; // how many bytes in this resource?
nID: Word; // the ID
end;
PGrpCursorDirEntry = ^TGrpCursorDirEntry;
TGrpCursorDirEntry = packed record
wWidth: Word; // Width, in pixels, of the image
wHeight: Word; // Height, in pixels, of the image
wPlanes: Word; // color planes
wBitCount: Word; // bits per pixel
dwBytesInRes: Dword; // how many bytes in this resource?
nID: Word; // the ID
end;
TLocalHeader = packed record
xHotSpot: Word;
yHotSpot: Word;
end;
PNewHeader = ^TNewHeader;
TNewHeader = packed record
idReserved: Word; // Reserved (must be 0)
idType: Word; // Resource type (1 for icons)
idCount: Word; // How many images?
end;
function TestStreamIsIcon(const AStream: TStream): boolean;
var
Signature: array[0..3] of char;
ReadSize: Integer;
OldPosition: TStreamSeekType;
begin
OldPosition:=AStream.Position;
ReadSize:=AStream.Read(Signature, SizeOf(Signature));
Result:=(ReadSize=SizeOf(Signature)) and CompareMem(@Signature,@IconSignature,4);
AStream.Position:=OldPosition;
end;
////////////////////////////////////////////////////////////////////////////////
{ TSharedIcon }
procedure TSharedIcon.FreeHandle;
begin
if FHandle = 0 then Exit;
DestroyIcon(FHandle);
FHandle := 0;
end;
procedure TSharedIcon.UpdateFromHandle(NewHandle: TLCLHandle);
var
Info: TIconInfo;
begin
FreeHandle;
FHandle := NewHandle;
// get the icon information
if WidgetSet.GetIconInfo(FHandle, @Info) then
Add(GetImagesClass.Create(Info));
end;
function TSharedIcon.IsEmpty: boolean;
begin
Result := inherited IsEmpty and (Count = 0);
end;
procedure TSharedIcon.Sort;
begin
FImages.Sort(@IconCompare);
end;
function TSharedIcon.GetImage(const AIndex: Integer): TIconImage;
begin
Result := TIconImage(FImages[AIndex]);
end;
class function TSharedIcon.GetImagesClass: TIconImageClass;
begin
Result := TIconImage;
end;
procedure TSharedIcon.Add(AIconImage: TIconImage);
begin
FImages.Add(AIconImage);
end;
constructor TSharedIcon.Create;
begin
inherited Create;
FImages := TFPList.Create;
end;
procedure TSharedIcon.Delete(AIndex: Integer);
var
Image: TIconImage;
begin
Image := TIconImage(FImages[AIndex]);
FImages.Delete(AIndex);
Image.Free;
end;
destructor TSharedIcon.Destroy;
begin
Clear;
FreeAndNil(FImages);
inherited Destroy;
end;
procedure TSharedIcon.Clear;
var
n: Integer;
begin
for n := 0 to FImages.Count - 1 do
TObject(FImages[n]).Free;
FImages.Clear;
end;
function TSharedIcon.GetIndex(AFormat: TPixelFormat; AHeight, AWidth: Word): Integer;
var
//List: TFPList;
Image: TIconImage;
begin
for Result := 0 to FImages.Count -1 do
begin
Image := TIconImage(FImages[Result]);
if Image.PixelFormat <> AFormat then Continue;
if Image.Height <> AHeight then Continue;
if Image.Width <> AWidth then Continue;
// found
Exit;
end;
Result := -1;
end;
function TSharedIcon.Count: Integer;
begin
Result := FImages.Count;
end;
////////////////////////////////////////////////////////////////////////////////
{ TIconImage }
constructor TIconImage.Create(AFormat: TPixelFormat; AHeight, AWidth: Word);
begin
inherited Create;
FHeight := AHeight;
FWidth := AWidth;
FPixelFormat := AFormat;
end;
constructor TIconImage.Create(const AImage: TRawImage);
begin
inherited Create;
UpdateFromImage(AImage);
end;
constructor TIconImage.Create(const AInfo: TIconInfo);
var
AImage: TRawImage;
begin
inherited Create;
FHandle := AInfo.hbmColor;
FMaskHandle := AInfo.hbmMask;
if RawImage_FromBitmap(AImage, FHandle, FMaskHandle) then
UpdateFromImage(AImage);
end;
destructor TIconImage.Destroy;
begin
if FHandle <> 0
then DeleteObject(FHandle);
FHandle := 0;
if FMaskHandle <> 0
then DeleteObject(FMaskHandle);
FMaskHandle := 0;
if FPalette <> 0
then DeleteObject(FPalette);
FPalette := 0;
FImage.FreeData;
inherited Destroy;
end;
function TIconImage.GetPalette: HPALETTE;
begin
// TODO: implement
Result := FPalette
end;
procedure TIconImage.RawImageNeeded(ADescOnly: Boolean);
var
ImagePtr: PRawImage;
Flags: TRawImageQueryFlags;
begin
ImagePtr := @FImage;
if ImagePtr^.Description.Format <> ricfNone
then begin
// description valid
if ADescOnly then Exit;
if (ImagePtr^.Data <> nil) and (ImagePtr^.DataSize > 0) then Exit;
if ImagePtr^.Description.Width = 0 then Exit; // no data
if ImagePtr^.Description.Height = 0 then Exit; // no data
end;
if FHandle <> 0
then begin
if ADescOnly
or not RawImage_FromBitmap(ImagePtr^, FHandle, FMaskHandle)
then ImagePtr^.Description := GetDescriptionFromBitmap(FHandle);
Exit;
end;
case PixelFormat of
pf1bit: Flags := [riqfMono, riqfMask];
pf4bit,
pf8bit: Flags := [riqfRGB, riqfMask, riqfPalette];
pf32bit: Flags := [riqfRGB, riqfMask, riqfAlpha];
else
Flags := [riqfRGB, riqfMask];
end;
ImagePtr^.Description := QueryDescription(Flags, Width, Height);
end;
procedure TIconImage.UpdateFromImage(const AImage: TRawImage);
begin
FImage := AImage;
FHeight := FImage.Description.Height;
FWidth := FImage.Description.Width;
case FImage.Description.Depth of
1: FPixelFormat := pf1Bit;
4: FPixelFormat := pf4Bit;
8: FPixelFormat := pf8Bit;
15: FPixelFormat := pf15Bit;
16: FPixelFormat := pf16Bit;
24: FPixelFormat := pf24Bit;
32: FPixelFormat := pf32Bit;
else
FPixelFormat := pfCustom;
end;
end;
function TIconImage.ReleaseHandle: HBITMAP;
begin
Result := Handle;
FHandle := 0;
end;
function TIconImage.ReleaseMaskHandle: HBITMAP;
begin
Result := MaskHandle;
FMaskHandle := 0;
end;
function TIconImage.ReleasePalette: HPALETTE;
begin
Result := Palette;
FPalette := 0;
end;
function TIconImage.UpdateHandles(ABitmap, AMask: HBITMAP): Boolean;
begin
Result := False;
if FHandle <> ABitmap
then begin
if FHandle <> 0
then DeleteObject(FHandle);
FHandle := ABitmap;
Result := True;
end;
if FMaskHandle <> AMask
then begin
if FMaskHandle <> 0
then DeleteObject(FMaskHandle);
FMaskHandle := AMask;
Result := True;
end;
end;
////////////////////////////////////////////////////////////////////////////////
{ TCustomIcon }
procedure TCustomIcon.Add(AFormat: TPixelFormat; AHeight, AWidth: Word);
begin
if GetIndex(AFormat, AHeight, AWidth) <> -1
then raise EInvalidGraphicOperation.Create(rsDuplicateIconFormat);
UnshareImage(True);
if TSharedIcon(FSharedImage).FImages.Add(TSharedIcon(FSharedImage).GetImagesClass.Create(AFormat, AHeight, AWidth)) = 0
then begin
// First added
FCurrent := 0;
UpdateCurrentView;
end;
end;
procedure TCustomIcon.Assign(Source: TPersistent);
begin
BeginUpdate;
if Source is TCustomIcon
then begin
FCurrent := -1;
end
else
if Source is TRasterImage
then begin
Clear;
with TRasterImage(Source) do
Self.Add(PixelFormat, Height, Width);
AssignImage(TRasterImage(Source));
EndUpdate;
Exit;
end;
inherited Assign(Source);
if Source is TCustomIcon
then begin
FCurrent := TCustomIcon(Source).Current;
// temporary hack since TRasterImage assign cannot handle multiply rawimages
if TCustomIcon(Source).GetSharedImageClass <> GetSharedImageClass
then UnshareImage(True);
end;
EndUpdate;
end;
procedure TCustomIcon.AssignImage(ASource: TRasterImage);
var
Image, NewImage: TIconImage;
RawImg: PRawImage;
RawMsk: TRawImage;
begin
if FCurrent = -1
then raise EInvalidGraphicOperation.Create(rsIconNoCurrent);
if ASource = nil
then raise EInvalidGraphicOperation.Create(rsIconImageEmpty);
Image := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]);
if (Image.Width <> ASource.Width)
or (Image.Height <> ASource.Height)
then raise EInvalidGraphicOperation.Create(rsIconImageSize);
if Image.PixelFormat <> ASource.PixelFormat
then raise EInvalidGraphicOperation.Create(rsIconImageFormat);
UnshareImage(True);
FreeCanvasContext;
RawImg := ASource.GetRawImagePtr;
NewImage := TSharedIcon(FSharedImage).GetImagesClass.Create(Image.PixelFormat, Image.Height, Image.Width);
try
NewImage.FImage.Description := RawImg^.Description;
// image
NewImage.FImage.DataSize := RawImg^.DataSize;
if NewImage.FImage.DataSize > 0
then begin
NewImage.FImage.Data := GetMem(NewImage.FImage.DataSize);
Move(RawImg^.Data^, NewImage.FImage.Data^, NewImage.FImage.DataSize);
end;
// mask
// in theory, it should not matter if a HBITMAP was created as bitmap or as mask
// since there is a description problem in gtk, create both (we cannot create mask only)
// todo: fix gtk
if ASource.MaskHandleAllocated
and RawImage_FromBitmap(RawMsk, ASource.MaskHandle, ASource.MaskHandle)
then begin
NewImage.FImage.MaskSize := RawMsk.MaskSize;
NewImage.FImage.Mask := RawMsk.Mask;
//// prevent cleanup
RawMsk.MaskSize := 0;
RawMsk.Mask := nil;
RawMsk.FreeData;
end
else begin
NewImage.FImage.MaskSize := RawImg^.MaskSize;
if NewImage.FImage.MaskSize > 0
then begin
NewImage.FImage.Mask := GetMem(NewImage.FImage.MaskSize);
Move(RawImg^.Mask^, NewImage.FImage.Mask^, NewImage.FImage.MaskSize);
end;
end;
// palette
NewImage.FImage.PaletteSize := RawImg^.PaletteSize;
if NewImage.FImage.PaletteSize > 0
then begin
NewImage.FImage.Palette := GetMem(NewImage.FImage.PaletteSize);
Move(RawImg^.Palette^, NewImage.FImage.Palette^, NewImage.FImage.PaletteSize);
end;
// this cannot be shcanged without adjusting data
// NewImage.FImage.Description.MaskBitsPerPixel := 1;
TSharedIcon(FSharedImage).FImages[FCurrent] := NewImage;
NewImage := nil;
Image.Free;
finally
NewImage.Free;
end;
Changed(Self);
end;
procedure TCustomIcon.Clear;
begin
if not Empty then
begin
FreeSaveStream;
FSharedImage.Release;
FSharedImage := GetSharedImageClass.Create;
FSharedImage.Reference;
FCurrent := -1;
Changed(Self);
end;
end;
function TCustomIcon.BitmapHandleAllocated: boolean;
begin
Result := (FCurrent <> -1) and (TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).FHandle <> 0);
end;
constructor TCustomIcon.Create;
begin
inherited Create;
FCurrent := -1;
FRequestedSize := Size(0, 0);
// per definition an icon is masked, but maybe we should make it settable for alpha images
FMasked := True;
end;
procedure TCustomIcon.Delete(Aindex: Integer);
begin
UnshareImage(True);
TSharedIcon(FSharedImage).Delete(AIndex);
if FCurrent = AIndex
then begin
FCurrent := -1;
UpdateCurrentView;
end
else if FCurrent > AIndex
then begin
Dec(FCurrent);
end;
end;
function TCustomIcon.GetCount: Integer;
begin
Result := TSharedIcon(FSharedImage).Count;
end;
procedure TCustomIcon.GetDescription(Aindex: Integer; out AFormat: TPixelFormat; out AHeight, AWidth: Word);
var
Image: TIconImage;
begin
Image := TIconImage(TSharedIcon(FSharedImage).FImages[Aindex]);
AFormat := Image.PixelFormat;
AHeight := Image.Height;
AWidth := Image.Width;
end;
class function TCustomIcon.GetFileExtensions: string;
begin
Result:='ico';
end;
function TCustomIcon.GetBitmapHandle: HBITMAP;
begin
if FCurrent = -1
then Result := 0
else begin
BitmapHandleNeeded;
Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).Handle;
end;
end;
class function TCustomIcon.GetDefaultSize: TSize;
begin
Result := Size(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
end;
function TCustomIcon.GetIndex(AFormat: TPixelFormat; AHeight, AWidth: Word): Integer;
begin
Result := TSharedIcon(FSharedImage).GetIndex(AFormat, AHeight, AWidth);
end;
function TCustomIcon.GetMaskHandle: HBITMAP;
begin
if FCurrent = -1
then Result := 0
else begin
MaskHandleNeeded;
Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).MaskHandle;
end;
end;
function TCustomIcon.GetPalette: HPALETTE;
begin
if FCurrent = -1
then Result := 0
else begin
PaletteNeeded;
Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).Palette;
end;
end;
function TCustomIcon.GetPixelFormat: TPixelFormat;
begin
if FCurrent = -1
then Result := pfCustom
else Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).PixelFormat;
end;
function TCustomIcon.GetRawImagePtr: PRawImage;
begin
if FCurrent = -1
then Result := nil
else begin
TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).RawImageNeeded(False);
Result := @TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).FImage;
end;
end;
function TCustomIcon.GetRawImageDescriptionPtr: PRawImageDescription;
begin
if FCurrent = -1
then Result := nil
else begin
TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).RawImageNeeded(True);
Result := @TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).FImage.Description;
end;
end;
function TCustomIcon.GetTransparent: Boolean;
begin
Result := True;
end;
class function TCustomIcon.GetStreamSignature: Cardinal;
begin
Result := 0;
end;
class function TCustomIcon.GetTypeID: Word;
begin
Result := 0;
end;
class function TCustomIcon.GetSharedImageClass: TSharedRasterImageClass;
begin
Result := TSharedIcon;
end;
procedure TCustomIcon.HandleNeeded;
begin
{$IFDEF VerboseLCLTodos}{$note TODO implement some WSclass call}{$ENDIF}
end;
function TCustomIcon.InternalReleaseBitmapHandle: HBITMAP;
begin
if FCurrent = -1
then Result := 0
else Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).ReleaseHandle;
end;
function TCustomIcon.InternalReleaseMaskHandle: HBITMAP;
begin
if FCurrent = -1
then Result := 0
else Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).ReleaseMaskHandle;
end;
function TCustomIcon.InternalReleasePalette: HPALETTE;
begin
if FCurrent = -1
then Result := 0
else Result := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).ReleasePalette;
end;
function TCustomIcon.LazarusResourceTypeValid(const ResourceType: string): boolean;
var
ResType: String;
begin
if Length(ResourceType) < 3 then Exit(False);
ResType := UpperCase(ResourceType);
case ResType[1] of
'I': begin
Result := (ResType = 'ICO') or (ResType = 'ICON');
end;
else
Result := inherited LazarusResourceTypeValid(ResType);
end;
end;
procedure TCustomIcon.LoadFromResourceName(Instance: TLCLHandle;
const ResName: String);
var
ResType: TResourceType;
ResHandle: TFPResourceHandle;
begin
ResType := GetResourceType;
if ResType = nil then Exit;
ResHandle := FindResource(Instance, PChar(ResName), PChar(ResType));
if ResHandle = 0 then
raise EResNotFound.Create(Format('[TCustomIcon.LoadFromResourceName] The resource "%s" was not found', [ResName])); // todo: valid exception
LoadFromResourceHandle(Instance, ResHandle);
end;
procedure TCustomIcon.LoadFromResourceID(Instance: TLCLHandle; ResID: PtrInt);
var
ResType: TResourceType;
ResHandle: TFPResourceHandle;
begin
ResType := GetResourceType;
if ResType = nil then Exit;
ResHandle := FindResource(Instance, PChar(ResID), PChar(ResType));
if ResHandle = 0 then
raise EResNotFound.Create(Format('[TCustomIcon.LoadFromResourceID] The resource #%d was not found', [ResID])); // todo: valid exception
LoadFromResourceHandle(Instance, ResHandle);
end;
procedure TCustomIcon.LoadFromResourceHandle(Instance: TLCLHandle;
ResHandle: TFPResourceHandle);
begin
end;
function TCustomIcon.MaskHandleAllocated: boolean;
begin
Result := (FCurrent <> -1) and (TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).FMaskHandle <> 0);
end;
procedure TCustomIcon.MaskHandleNeeded;
var
ImagePtr: PRawImage;
ImgHandle, dummy: HBITMAP;
MaskImage: TRawImage;
begin
if FCurrent = -1 then Exit;
if MaskHandleAllocated then exit;
ImagePtr := GetRawImagePtr;
if (ImagePtr = nil) or
(ImagePtr^.Description.Format = ricfNone) or
(ImagePtr^.Description.MaskBitsPerPixel = 0) then
Exit;
MaskImage.Init;
MaskImage.Description := ImagePtr^.Description.GetDescriptionFromMask;
MaskImage.DataSize := ImagePtr^.MaskSize;
MaskImage.Data := ImagePtr^.Mask;
// CreateCompatibleBitmaps cannot work with empty Data => create dummy data
if ImagePtr^.Mask = nil then
MaskImage.CreateData(True);
if CreateCompatibleBitmaps(MaskImage, ImgHandle, Dummy, True)
then begin
if BitmapHandleAllocated
then UpdateHandles(BitmapHandle, ImgHandle)
else UpdateHandles(0, ImgHandle);
end
else
{$IFNDEF DisableChecks}
DebugLn('TCustomIcon.MaskHandleNeeded: Unable to create maskhandle')
{$ENDIF};
if ImagePtr^.Mask = nil then
MaskImage.FreeData;
end;
function TCustomIcon.PaletteAllocated: boolean;
begin
Result := (FCurrent <> -1) and (TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]).FPalette <> 0);
end;
procedure TCustomIcon.PaletteNeeded;
begin
// nothing to do, handled by image itself
end;
function TCustomIcon.CanShareImage(AClass: TSharedRasterImageClass): Boolean;
begin
// temporary hack to make Assign work between cursors, icons and icnsicons
Result := AClass.InheritsFrom(TSharedIcon);
end;
procedure TCustomIcon.CheckRequestedSize;
begin
if (FRequestedSize.cx = 0) and (FRequestedSize.cy = 0) then
FRequestedSize := GetDefaultSize;
// if someone set only height then set width = height
if FRequestedSize.cx = 0 then
FRequestedSize.cx := FRequestedSize.cy;
// if someone set only width then set height = width
if FRequestedSize.cy = 0 then
FRequestedSize.cy := FRequestedSize.cx;
end;
procedure TCustomIcon.ReadData(Stream: TStream);
var
Signature: array [0..3] of Byte;
Size: longint absolute Signature;
Position: Int64;
begin
// Check it the stream is prefixed with a size.
// Delphi doesn't, while we do.
Position := Stream.Position;
Stream.Read(Signature, SizeOf(Signature));
Stream.Position := Position;
if Cardinal(Signature) = GetStreamSignature
then begin
// Assume Icon - stream without explicit size
LoadFromStream(Stream);
end
else begin
// use inherited to read, so "old" streams are converted
inherited;
end;
end;
procedure TCustomIcon.ReadStream(AStream: TMemoryStream; ASize: Longint);
var
Header: TIconHeader;
StreamStart: Int64;
IconDir: array of TIconDirEntry;
n: Integer;
IconImage: TIconImage;
IntfImage: TLazIntfImage;
PNGSig: array[0..7] of Byte;
PNGReader: TLazReaderPNG;
DIBReader: TLazReaderDIB;
ImgReader: TFPCustomImageReader;
LazReader: ILazImageReader;
RawImg: TRawImage;
begin
StreamStart := AStream.Position;
AStream.Read(Header, SizeOf(Header));
{$ifdef FPC_BIG_ENDIAN}
// adjust header
Header.idType := LEtoN(Header.idType);
Header.idCount := LEtoN(Header.idCount);
{$endif}
if (Header.idType <> 1) and (Header.idType <> 2)
then raise EInvalidGraphic.Create('Stream is not an Icon type');
if Header.idCount = 0
then begin
AStream.Seek(StreamStart + ASize, soBeginning);
FCurrent := -1;
Exit;
end;
SetLength(IconDir, Header.idCount);
AStream.Read(IconDir[0], Header.idCount * SizeOf(IconDir[0]));
PNGReader := nil;
DIBReader := nil;
IntfImage := nil;
try
for n := 0 to Header.idCount - 1 do
begin
{$ifdef FPC_BIG_ENDIAN}
// adjust entry
IconDir[n].wXHotSpot := LEtoN(IconDir[n].wXHotSpot);
IconDir[n].wYHotSpot := LEtoN(IconDir[n].wYHotSpot);
IconDir[n].dwBytesInRes := LEtoN(IconDir[n].dwBytesInRes);
IconDir[n].dwImageOffset := LEtoN(IconDir[n].dwImageOffset);
{$endif}
AStream.Seek(StreamStart + IconDir[n].dwImageOffset, soBeginning);
ImgReader := nil;
if (IconDir[n].bWidth = 0) or (IconDir[n].bHeight = 0)
then begin
// PNG or DIB image
// Vista icons are PNG in this case, but there exist also "old style" icons
// with DIB image
// don't use PNGReader.CheckContents(AStream) since it uses internally
// an exception for checking, which is not "nice" when debugging.
AStream.Read(PNGSig, SizeOf(PNGSig));
AStream.Seek(StreamStart + IconDir[n].dwImageOffset, soBeginning);
if QWord(PNGComn.Signature) = QWord(PNGSig)
then begin
if PNGReader = nil
then PNGReader := TLazReaderPNG.Create;
ImgReader := PNGReader;
end;
end;
if ImgReader = nil
then begin
// DIB image
if DIBReader = nil
then DIBReader := TLazReaderIconDIB.Create;
ImgReader := DIBReader;
end;
// create or reset intfimage
if IntfImage = nil
then IntfImage := TLazIntfImage.Create(0,0,[])
else IntfImage.SetSize(0,0);
if Supports(ImgReader, ILazImageReader, LazReader)
then LazReader.UpdateDescription := True
else IntfImage.DataDescription := QueryDescription([riqfRGB, riqfAlpha, riqfMask]); // fallback to default
ImgReader.ImageRead(AStream, IntfImage);
// Add image
IntfImage.GetRawImage(RawImg, True);
// Paul: don't set MaskBitsPerPixel to zero => windows will fail with no mask
// Even empty mask is better than no mask. But maybe CreateIconIndirect must be fixed on windows?
RawImg.Description.MaskBitsPerPixel := 1;
with TSharedIcon(FSharedImage) do
begin
IconImage := GetImagesClass.Create(RawImg);
if IconImage is TCursorImageImage then
TCursorImageImage(IconImage).HotSpot := Point(IconDir[n].wXHotSpot, IconDir[n].wYHotSpot);
FImages.Add(IconImage);
end;
end;
finally
LazReader := nil;
DIBReader.Free;
PNGReader.Free;
IntfImage.Free;
end;
// Adjust all entries and find best (atm the order: best width, best height, max depth)
CheckRequestedSize;
FCurrent := GetBestIndexForSize(FRequestedSize);
end;
procedure TCustomIcon.Remove(AFormat: TPixelFormat; AHeight, AWidth: Word);
var
idx: Integer;
begin
idx := GetIndex(AFormat, AHeight, AWidth);
if idx <> -1 then Delete(idx);
end;
procedure TCustomIcon.SetCurrent(const AValue: Integer);
begin
if FCurrent = AValue then exit;
FCurrent := AValue;
UpdateCurrentView;
end;
procedure TCustomIcon.SetHandles(ABitmap, AMask: HBITMAP);
begin
{$IFDEF VerboseLCLTodos}{$note Implement me (or raise exception)}{$ENDIF}
end;
procedure TCustomIcon.SetMasked(AValue: Boolean);
begin
// nothing
end;
function TCustomIcon.GetBestIndexForSize(ASize: TSize): Integer;
var
BestDepth, i, dx, dy, dd: Integer;
CurRawImage: TRawImage;
ScreenDC: HDC;
begin
Result := -1;
if ASize.cx <= 0 then
begin
ASize.cx := GetSystemMetrics(SM_CXICON);
if ASize.cx = -1 then
ASize.cx := 32;
end;
if ASize.cy <= 0 then
begin
ASize.cy := GetSystemMetrics(SM_CYICON);
if ASize.cy = -1 then
ASize.cy := 32;
end;
ScreenDC := GetDC(0);
BestDepth := GetDeviceCaps(ScreenDC, BITSPIXEL);
ReleaseDC(0, ScreenDC);
dx := MaxInt;
dy := MaxInt;
dd := MaxInt;
for i := 0 to Count - 1 do
begin
CurRawImage := TIconImage(TSharedIcon(FSharedImage).FImages[i]).FImage;
if Abs(ASize.cx - CurRawImage.Description.Width) < dx then
begin
dx := Abs(ASize.cx - CurRawImage.Description.Width);
Result := i;
end
else
if Abs(ASize.cx - CurRawImage.Description.Width) = dx then
begin
if Abs(ASize.cy - CurRawImage.Description.Height) < dy then
begin
dy := Abs(ASize.cy - CurRawImage.Description.Height);
Result := i;
end
else
if Abs(ASize.cy - CurRawImage.Description.Height) = dy then
begin
if Abs(BestDepth - CurRawImage.Description.Depth) < dd then
begin
dd := Abs(BestDepth - CurRawImage.Description.Depth);
Result := i;
end;
end;
end
end;
end;
procedure TCustomIcon.SetPixelFormat(AValue: TPixelFormat);
begin
raise EInvalidGraphicOperation.Create(rsIconImageFormatChange);
end;
procedure TCustomIcon.SetTransparent(Value: Boolean);
begin
// nothing
end;
procedure TCustomIcon.Sort;
var
ACurrent: Pointer;
begin
if FCurrent>=0 then
ACurrent := TSharedIcon(FSharedImage).FImages[FCurrent]
else
ACurrent := nil;
TSharedIcon(FSharedImage).Sort;
if ACurrent<>nil then
FCurrent := TSharedIcon(FSharedImage).FImages.IndexOf(ACurrent);
end;
procedure TCustomIcon.SetSize(AWidth, AHeight: integer);
begin
if FCurrent <> -1
then raise EInvalidGraphicOperation.Create(rsIconImageSizeChange)
else FRequestedSize := Size(AWidth, AHeight);
end;
procedure TCustomIcon.UnshareImage(CopyContent: boolean);
var
NewIcon, OldIcon: TSharedIcon;
n: Integer;
OldImage, NewImage: TIconImage;
OldSharedImage: TSharedImage;
begin
if FSharedImage.RefCount <= 1 then Exit;
NewIcon := GetSharedImageClass.Create as TSharedIcon;
try
NewIcon.Reference;
if CopyContent
then begin
OldIcon := FSharedImage as TSharedIcon;
for n := 0 to OldIcon.FImages.Count -1 do
begin
OldImage := TIconImage(OldIcon.FImages[n]);
NewImage := NewIcon.GetImagesClass.Create(OldImage.PixelFormat, OldImage.Height, OldImage.Width);
NewIcon.FImages.Add(NewImage);
NewImage.FImage.Description := OldImage.FImage.Description;
NewImage.FImage.DataSize := OldImage.FImage.DataSize;
if NewImage.FImage.DataSize > 0
then begin
NewImage.FImage.Data := GetMem(NewImage.FImage.DataSize);
Move(OldImage.FImage.Data^, NewImage.FImage.Data^, NewImage.FImage.DataSize);
end;
NewImage.FImage.MaskSize := OldImage.FImage.MaskSize;
if NewImage.FImage.MaskSize > 0
then begin
NewImage.FImage.Mask := GetMem(NewImage.FImage.MaskSize);
Move(OldImage.FImage.Mask^, NewImage.FImage.Mask^, NewImage.FImage.MaskSize);
end;
NewImage.FImage.PaletteSize := OldImage.FImage.PaletteSize;
if NewImage.FImage.PaletteSize > 0
then begin
NewImage.FImage.Palette := GetMem(NewImage.FImage.PaletteSize);
Move(OldImage.FImage.Palette^, NewImage.FImage.Palette^, NewImage.FImage.PaletteSize);
end;
end;
end;
FreeCanvasContext;
OldSharedImage := FSharedImage;
FSharedImage := NewIcon;
NewIcon := nil; // transaction sucessful
OldSharedImage.Release;
finally
// in case something goes wrong, keep old and free new
NewIcon.Free;
end;
end;
procedure TCustomIcon.UpdateCurrentView;
begin
FreeCanvasContext;
Changed(Self);
end;
procedure TCustomIcon.SetHandle(AValue: TLCLHandle);
begin
if FSharedImage.FHandle <> AValue
then begin
// if the handle is set externally we should unshare ourselves
FreeCanvasContext;
UnshareImage(false);
FreeSaveStream;
TSharedIcon(FSharedImage).Clear;
end;
if UpdateHandle(AValue)
then begin
if (TSharedIcon(FSharedImage).Count > 0) then
FCurrent := 0
else
FCurrent := -1;
Changed(Self);
end;
end;
function TCustomIcon.UpdateHandle(AValue: HICON): Boolean;
begin
Result := FSharedImage.FHandle <> AValue;
if Result then
TSharedIcon(FSharedImage).UpdateFromHandle(AValue);
end;
function TCustomIcon.UpdateHandles(ABitmap, AMask: HBITMAP): Boolean;
var
Image: TIconImage;
begin
if FCurrent = -1
then begin
Result := False;
Exit;
end;
Image := TIconImage(TSharedIcon(FSharedImage).FImages[FCurrent]);
Result := Image.UpdateHandles(ABitmap, AMask);
end;
procedure TCustomIcon.WriteStream(AStream: TMemoryStream);
procedure GetMaskData(ARawImg: TRawImage; AIconImage: TIconImage; AMskPtr: Pointer; AMskSize: Cardinal);
var
SrcRawImg, DstRawImg: TRawImage;
SrcDesc: TRawImageDescription absolute SrcRawImg.Description;
DstDesc: TRawImageDescription absolute DstRawImg.Description;
SrcImage, DstImage: TLazIntfImage;
NeedFreeData: Boolean;
begin
NeedFreeData := True;
if (AIconImage.MaskHandle = 0)
or not RawImage_FromBitmap(SrcRawImg, AIconImage.MaskHandle, 0)
then begin
SrcRawImg.Init;
SrcRawImg.Description := ARawImg.Description.GetDescriptionFromMask;
SrcRawImg.Data := ARawImg.Mask;
SrcRawImg.DataSize := ARawImg.MaskSize;
NeedFreeData := False;
end;
DstRawImg.Init;
DstRawImg.Data := AMskPtr;
DstRawImg.DataSize := AMskSize;
DstDesc.Format := ricfGray;
DstDesc.Width := AIconImage.Width;
DstDesc.Height := AIconImage.Height;
DstDesc.Depth := 1;
DstDesc.BitOrder := riboReversedBits;
DstDesc.ByteOrder := riboLSBFirst;
DstDesc.LineOrder := riloBottomToTop;
DstDesc.LineEnd := rileDWordBoundary;
DstDesc.BitsPerPixel := 1;
DstDesc.RedPrec := 1;
DstDesc.RedShift := 0;
if SrcDesc.IsEqual(DstDesc)
then begin
Move(ARawImg.Mask^, AMskPtr^, ARawImg.MaskSize);
Exit;
end;
SrcImage := TLazIntfImage.Create(SrcRawImg, False);
DstImage := TLazIntfImage.Create(DstRawImg, False);
DstImage.CopyPixels(SrcImage);
SrcImage.Free;
DstImage.Free;
if NeedFreeData then
SrcRawImg.FreeData;
end;
var
Header: TIconHeader;
StreamStart: Int64;
IconDir: array of TIconDirEntry;
n: Integer;
ImageCount: Word;
IconImage: TIconImage;
IntfImage: TLazIntfImage;
PNGWriter: TFPWriterPNG;
BMPWriter: TFPWriterBMP;
BmpPtr: PByte;
MskPtr: PByte;
MskSize: Cardinal;
MemStream: TMemoryStream;
RawImg: TRawImage;
begin
ImageCount := TSharedIcon(FSharedImage).Count;
StreamStart := AStream.Position;
Header.idReserved := 0;
Header.idType := NtoLE(GetTypeID);
Header.idCount := LEtoN(ImageCount);
AStream.Write(Header, SizeOf(Header));
if ImageCount = 0 then Exit;
SetLength(IconDir, ImageCount);
FillChar(IconDir[0], ImageCount * SizeOf(IconDir[0]), 0);
// write empty dirlist first, so the images can be written after it.
// we'll update it later
AStream.Write(IconDir[0], ImageCount * SizeOf(IconDir[0]));
PNGWriter := nil;
BMPWriter := nil;
MemStream := nil;
IntfImage := nil;
try
for n := 0 to ImageCount - 1 do
begin
IconImage := TIconImage(TSharedIcon(FSharedImage).FImages[n]);
RawImg := IconImage.FImage;
// set offset
IconDir[n].dwImageOffset := NtoLE(DWord(AStream.Position - StreamStart));
// create or reset intfimage
if IntfImage = nil
then IntfImage := TLazIntfImage.Create(RawImg, False)
else IntfImage.SetRawImage(RawImg, False);
// user temp mem stream for storage.
if MemStream = nil
then MemStream := TMemoryStream.Create
else MemStream.Position := 0;
// write image data
if (IconImage.Width >= 255) or (IconImage.Height >= 255)
then begin
// PNG or DIB image
// Vista icons are PNG in this case, but there exist also "old style" icons
// with DIB image, we use PNG
// (dir.width and dir.height stay 0 in this case)
if PNGWriter = nil
then begin
PNGWriter := TFPWriterPNG.Create;
PNGWriter.Indexed := False;
PNGWriter.WordSized := False;
end;
PNGWriter.GrayScale := RawImg.Description.Format = ricfGray;
PNGWriter.UseAlpha := RawImg.Description.AlphaPrec > 0;
PNGWriter.ImageWrite(MemStream, IntfImage);
IconDir[n].wBpp := NtoLE(Word(RawImg.Description.BitsPerPixel));
IconDir[n].dwBytesInRes := NtoLE(DWord(MemStream.Position));
MemStream.SaveToStream(AStream);
end
else begin
// DIB image
IconDir[n].bHeight := IconImage.Height;
IconDir[n].bWidth := IconImage.Width;
// since there is no DIB writer, write a BMP to a temp stream and skip the file header
if BMPWriter = nil
then begin
BMPWriter := TFPWriterBMP.Create;
BMPWriter.RLECompress := False;
end;
case IconImage.PixelFormat of
pfDevice: BMPWriter.BitsPerPixel := QueryDescription([riqfRGB]).BitsPerPixel;
pfCustom: BMPWriter.BitsPerPixel := RawImg.Description.BitsPerPixel;
else
BMPWriter.BitsPerPixel := PIXELFORMAT_BPP[IconImage.PixelFormat];
end;
BMPWriter.ImageWrite(MemStream, IntfImage);
// adjust BMP data so it is a IconDIB
BmpPtr := PByte(MemStream.Memory) + SizeOf(TBitMapFileHeader);
// double the height to accommodate the mask
PBitMapInfoHeader(BmpPtr)^.biHeight := NtoLE(LEtoN(PBitMapInfoHeader(BmpPtr)^.biHeight) * 2);
// write mask.
// align to dword
MskSize := (((IconImage.Width + 31) shr 5) shl 2) * IconImage.Height;
// alloc "buffer"
if MemStream.Size < MemStream.Position + MskSize
then begin
MemStream.Size := MemStream.Position + MskSize;
// reallocation, recalculate bmpptr
BmpPtr := PByte(MemStream.Memory) + SizeOf(TBitMapFileHeader);
end;
MskPtr := PByte(MemStream.Memory) + MemStream.Position;
MemStream.Seek(MskSize, soCurrent);
if (RawImg.Mask = nil)
or (RawImg.MaskSize = 0)
then FillChar(MskPtr^, MskSize, 0)
else GetMaskData(RawImg, IconImage, MskPtr, MskSize);
// write
AStream.WriteBuffer(BmpPtr^, MemStream.Position - SizeOf(TBitMapFileHeader));
IconDir[n].dwBytesInRes := NtoLE(DWord(MemStream.Position - SizeOf(TBitMapFileHeader)));
IconDir[n].wBpp := NtoLE(Word(BMPWriter.BitsPerPixel));
end;
if IconImage is TCursorImageImage
then begin
IconDir[n].wXHotSpot := NtoLE(Word(TCursorImageImage(IconImage).HotSpot.X));
IconDir[n].wYHotSpot := NtoLE(Word(TCursorImageImage(IconImage).HotSpot.Y));
end
else begin
IconDir[n].wPlanes := NtoLE(Word(1));
end;
end;
finally
PNGWriter.Free;
BMPWriter.Free;
MemStream.Free;
IntfImage.Free;
end;
// update directory
AStream.Seek(StreamStart + SizeOf(Header), soBeginning);
AStream.Write(IconDir[0], ImageCount * SizeOf(IconDir[0]));
end;
////////////////////////////////////////////////////////////////////////////////
{ TIcon }
function TIcon.GetIconHandle: HICON;
begin
Result := GetHandle;
end;
class function TIcon.GetTypeID: Word;
begin
Result := 1; //icon
end;
function TIcon.ReleaseHandle: HICON;
// simply return the current handle and set to 0 without freeing handles
begin
HandleNeeded;
Result := FSharedImage.ReleaseHandle;
end;
function TIcon.GetResourceType: TResourceType;
begin
Result := RT_GROUP_ICON;
end;
procedure TIcon.SetIconHandle(const AValue: HICON);
begin
SetHandle(AValue);
end;
class function TIcon.GetStreamSignature: Cardinal;
begin
Result := Cardinal(IconSignature);
end;
procedure TIcon.HandleNeeded;
var
IconInfo: TIconInfo;
begin
if FSharedImage.FHandle <> 0 then Exit;
IconInfo.fIcon := True;
IconInfo.hbmColor := BitmapHandle;
IconInfo.hbmMask := MaskHandle;
FSharedImage.FHandle := WidgetSet.CreateIconIndirect(@IconInfo);
end;
procedure TIcon.LoadFromResourceHandle(Instance: TLCLHandle;
ResHandle: TFPResourceHandle);
var
GlobalHandle: TFPResourceHGlobal;
Dir: PNewHeader;
DirEntry: PGrpIconDirEntry;
IconEntry: TIconDirEntry;
i, offset: integer;
Stream: TMemoryStream;
IconStream: TResourceStream;
begin
// build a usual ico stream using several RT_ICON resources
GlobalHandle := LoadResource(Instance, ResHandle);
if GlobalHandle = 0 then
Exit;
Dir := LockResource(GlobalHandle);
if Dir = nil then
Exit;
Stream := TMemoryStream.Create;
try
// write icon header
Stream.Write(Dir^, SizeOf(TIconHeader));
// write icon entries headers
offset := Stream.Position + SizeOf(IconEntry) * LEtoN(Dir^.idCount);
DirEntry := PGrpIconDirEntry(PChar(Dir) + SizeOf(Dir^));
for i := 0 to LEtoN(Dir^.idCount) - 1 do
begin
Move(DirEntry^, IconEntry, SizeOf(DirEntry^));
IconEntry.dwImageOffset := NtoLE(offset);
inc(offset, LEtoN(IconEntry.dwBytesInRes));
Stream.Write(IconEntry, SizeOf(IconEntry));
Inc(DirEntry);
end;
// write icons data
DirEntry := PGrpIconDirEntry(PChar(Dir) + SizeOf(Dir^));
for i := 0 to LEtoN(Dir^.idCount) - 1 do
begin
IconStream := TResourceStream.CreateFromID(Instance, LEtoN(DirEntry^.nID), RT_ICON);
try
Stream.CopyFrom(IconStream, IconStream.Size);
finally
IconStream.Free;
end;
Inc(DirEntry);
end;
Stream.Position := 0;
ReadData(Stream);
finally
Stream.Free;
UnLockResource(GlobalHandle);
FreeResource(GlobalHandle);
end;
end;