Loading specific icon size into TIcon from stream - delphi

My application downloads and displays favicons for specific websites. I followed Bing's solution for detecting image format from stream, but have hit another snag. Assuming an actual icon image, the code goes like this:
var
icon : TIcon;
begin
icon := TIcon.Create;
try
icon.LoadFromStream( faviconStream );
spFavicon.Glyph.Assign( icon );
finally
icon.Free;
end;
end;
(spFavicon is TRzGlyphStatus from Raize Components. Its Glyph property is a TBitmap)
Now, this works, but sometimes the downloaded icon contains multiple images in different sizes, e.g. 32x32 in addition to the expected 16x16. For some reason the control's Glyph property picks the larger size.
How can I load only the 16x16 size into TIcon, or from TIcon into TBitmap?
Test favicon: http://www.kpfa.org/favicon.ico
On edit: If at all possible, I'd rather avoid saving the icon to a file first.

The primary source for the file format of an .ico file is on MSDN. You should be able to work it out from this.
The ReadIcon procedure in Graphics.pas may be of use but I imagine that you only need to find 16x16 since you are looking for favicons.
If you wanted to get really cute, you could download the source to, say, Firefox, and see exactly how they handle favicons.

Related

TJpegImage: Internal bitmap not updated after applying JPEG compression

I want to convert a BMP to JPG, compress that JPG and the put back the compressed JPG into the original BMP.
However, it won't assign the compressed image to the BMP. I always get the orignal image into the BMP.
The code is below. To see the compression I set CompressionQuality = 1. This will literary ruin the image.
function CompressBmp2RAM(InOutBMP: TBitmap): Integer;
VAR
Stream: TMemoryStream;
Jpg: TJPEGImage;
begin
Jpg:= TJPEGImage.Create;
Stream:= TMemoryStream.Create;
TRY
Jpg.Assign(InOutBMP);
Jpg.CompressionQuality:= 1; // highest compression, lowest quality
Jpg.Compress;
Jpg.SaveToStream(Stream);
//Stream.SaveToFile('c:\out.jpg'); <---- this gives the correct (heavily compressed) image
Result:= tmpQStream.Size;
InOutBMP.Assign(Jpg);
//InOutBMP.SaveToFile('c:\out.bmp'); <---- this gives the uncompressed image
FINALLY
FreeAndNil(Stream);
FreeAndNil(Jpg);
END;
end;
I have found an work around, but I still want to know why InOutBMP.Assign(Jpg) in the code above won't work.
...
Stream.Position:= 0;
Jpg.LoadFromStream(Stream);
InOutBMP.Assign(Jpg);
...
To me it seems to be a bug. The JPG is not aware that the data was recompressed, so the internal bitmap is never updated. There should be some kind of internal "DirtyData" (or "HasChanged") flag.
So, what is the official solution for this?
Having the JPG to reload ITS OWN DATA from an external data source (stream) seems rather a hack/temporary bug fix.
PS: Jpg.DIBNeeded won't help.
I just checked the code of TJpegImage, and the hypothesis I posted in the comments seems correct.
TJpegImage keeps an internal TBitmap for the representation. When you call DIBNeeded, this bitmap is created based on the Jpeg image data.
GetBitmap (the private function that does the legwork for DIBNeeded) will first check if the bitmap is already assigned, and won't repeat the process if it is. So just calling DIBNeeded will not work in your case, since you're basically guaranteed to have this cached bitmap already.
The FreeBitmap method will free the internal bitmap, after which calling DIBNeeded will create a new one again. So I think the sequence you need is:
Jpg.Compress; // Make sure the Jpeg compressed image data is up to date
Jpg.FreeBitmap; // Clear the internal cached bitmap
Jpg.DIBNeeded; // Optional, get a new bitmap. Will happen when you assign to TBitmap.
I also mentioned JpegNeeded before, but that will do a similar thing as DIBNeeded: check if there is data, if not, call Compress. So you need to call Compress, like you did, to force this compression.
PS: TBitmap (and file formats similar to bmp), don't really know this kind of compression, so by assigning it back to the bitmap, you will have reduced image quality, but not image size. Some bitmap formats, including PNG, do compress by using (amongst others) run length encoding (RLE), which means something like spending just four bytes for saying "And now, 54 times a pixel of this color!". That kind of compression won't work really well on images with lots of jpeg artifacts (the grainy/blurry side effect of the compression), so a PNG version of the compressed Jpg might be larger than a PNG version of the original, even though the quality of the original is better as well. This is especially true for images with large areas of the same color, like screenshots and certain artwork.
The internal TJPEGIMage.GetBitmap function only creates an internal bitmap based on the current jpeg image if no internal bitmap has been previously created.
Assign Image A. Do not use Canvas.
Assign Image B. Use Canvas and see Image B.
Assign Image C. Use Canvas and see Image B.
Assign Image D. Use Canvas and see Image B.
etc.
This is definitely a bug in TJPEGImage. I'm using XE7, so maybe this has been fixed by now.
My workaround to always get the correct JPEGImage.Canvas bitmap is to clear any existing internal bitmap before the assignment.
TJPEGImage = class(Vcl.Imaging.jpeg.TJPEGImage)
public
procedure Assign(Source: TPersistent); override;
end;
procedure TJPEGImage.Assign(Source: TPersistent);
begin
FreeBitmap;
inherited;
end;
The code above is helpful when using one TJPEGImage to handle a lot of different images. For only a few images, creating a new TJPEGImage for each image works.

How to add design time icon for FireMonkey component?

I have read this article by Paweł Głowacki and I have been able to display an icon for my component. The result is the following:
I can see the image in the Tool Palette and in the structure view. By the way in the designer I see the default icon:
How can I display the icon of my component in the designer as well?
I am using Delphi Tokyo 10.2 Update 2. I have followed the article I have linked to get the image shown. My component is the following:
type
TEquationSolver = class(TComponent)
//code...
end;
Basically, I have done the following:
I have created 3 bitmaps (16x16 24x24 32x32) and a png (128x128)
I have added them as resources going into Project > Resources and Images
I have called them TEquationSolver with the suffix that indicates the size. In this way they are properly displayed on the IDE.
What am I missing for the design time part? In this article I have read the following:
Our guide is: Use PNG if you want very easy backwards compatibility,
or small file (BPL) size; use bitmaps if you want fast loading. We
use bitmaps for 16, 24, and 32px icons, and PNG for the 128px icons.
In fact I have 16x16, 24x24, 32x32 bitmaps and the 128px png. Is there something else?
It seems that you have to create the files I have shown above plus the image for the Designer. The latter needs to match the name of the class that inherits from TComponent (in my case):
As you can see I have added another bitmap (it's the 32x32, I have just made a copy and changed the name) and I have used TEquationSolver as name. After a Build + Install I have the following result:
Note that I could have added only logo.bmp as resource (with the ID that matches the TComponent-derived class name) and it would have worked anyway. The problem is that the pictures won't be neat because they'll be resized and they may appear blurred, like in my case.
For this reason I think that it's good:
Put an image that will appear in the form designer
Put the 16x16, 24x24, 32x32 and 128x128 that will be used by the IDE
The difference is evident in my case. When I have only a single bitmap the quality of the image is low but when I provide the various sizes they look better.

Clipping a filmstrip in png format (Delphi 2010)

I have a filmstrip of images in png format like this:
I'd like to know how to clip each of the images and put these images in a TImageList control, always preserving the transparency.
[EDIT]
Yes, at designtime the trick mentioned by RRUZ works fine, but I wanted to clip the images at runtime, i.e. by loading the filmstrip from resource or file
You must follow these steps:
set ColorDepth property to cd32Bit,
DrawingStyle to dsTransparent,
Height= 48,
Width=48,
then load the image and the result will be
Just import into the imagelist. It'll complain that it's too big and offer to break it into pieces for you. Works fine for me on D2005.
Another cool tip: I use AWIcons Pro http://www.awicons.com/icon-editor/ to edit icons (nice editor!). It has a feature that can export an icon as an imagelist (.bmp or .png format), thus making the filmstrip out of an icon. This makes it really handy to edit these things in .ico format, with a series of cells all the same size and depth, with each cell varying slightly. Then you export as an imagelist (I use .png) and then Delphi can break them back out into individual cells. Very slick. AWIcons isn't free, but features like this really make it productive.
At runtime, you would have to call TImageList.FileLoad. Except it won't work.
This in turn calls ImageList_LoadImage, with uFlags parameter value including the bit LR_LOADFROMFILE, which causes Windows to load from a file on disk. This underlying functionality only supports TBitmap (BMP) format.
See the nearly-duplicate question. PNG support is a designtime feature that is converting the PNG data into an internal non-PNG and not-exactly-a-BMP-either format, used internally by MS Common Controls library. View your DFM as text, and you will see what your PNG inputs have been turned into. The other answers show you that transparency is preserved, using bitmap-color based transparency.
If you want to keep your data in PNG format, you shouldn't be using a VCL TImageList to store it, because you're going to have to do a conversion from PNG to TBitmap to actually use TImageList.

Delphi and 48x48 (or bigger) imagelists - is there a workaround?

I'm getting the system imagelist (with SHGetFileInfo and SHGFI_LARGEICON), adding two of my own icons and attaching it to a TListView.
The problem is that if the user's icon size isn't set to 32x32 (like it's set to 48x48 for example) the Delphi7 TImageList fails with an "Invalid image size" error.
Does anyone know if a workaround is available? I've tried using TPngImageList but it leads to other issues.
Also, please note that I'd like to preserve the Alpha channel of the icons. Normal 1-bit transparency is not enough, as icons tend to look ugly that way.
Thanks!
I'm not aware of any limitation on the size of images that TImageList can hold. It sounds to me that your problem is that you have icons of different sizes and you can't hold icons of different sizes in the same image list.
If you are working with icons of different sizes then you are going to need to grow the smaller ones in size. You'll have to build it up in code, using a bitmap. You fill the bitmap with pure transparent alpha channel and then blt the smaller icon onto the centre of the bitmap.
Another option would be to maintain two separate image lists but if you need to draw the icons into the same list view then I think that won't get the job done. My guess is that you'll need to grow the small icons.
For alpha, you're going to need to create the image list handle yourself because the ColorDepth property doesn't exist in D7. Because of this, a vanilla D7 TImageList simply cannot support icons with alpha channels.
You work around this limitation by calling ImageList_Create, passing ILC_COLOR32 and assigning the result to ImageList.Handle. Do this before you add any images. You'll have to populate the list at run time rather than design time, but it sounds like you are already doing that.
Here's a screen shot of a 48x48 tool button with a 32bpp icon with alpha transparency:
It's true that I made this in D2010, but my above workaround will work for D7 – I used that mechanism until quite recently with D6. I'm just showing this to prove that the image list can hold 48px icons. Since TImageList is just a wrapper around the system image list component, I believe what you are attempting should be perfectly feasible.
Just when I was about to give up this page led me to the solution:
http://delphihaven.wordpress.com/2010/09/06/custom-drawing-on-glass-2/
Apparently, if you try to add an icon that is bigger than 32x32 to a timagelist in Delphi7, the VCL will give you an "Invalid image size" error while it could simply call the himagelist API - which can easily handle it.
Here is the complete solution:
unit ImageListFix;
interface
uses CommCtrl, Graphics, ImgList;
type
TImageListFixer = class(TCustomImageList)
public
function AddIcon(Image: TIcon): Integer;
end;
implementation
function TImageListFixer.AddIcon(Image: TIcon): Integer;
begin
if Image = nil then
Result := Add(nil, nil)
else
begin
Result := ImageList_AddIcon(Handle, Image.Handle);
Change;
end;
end;
end.
And the code for adding icons to the system imagelist:
DocumentImgList:=TImageListFixer(GetSystemLargeIconsList);
IconToAdd:=TIcon.Create;
try
IconToAdd.Handle := LoadImage(0, 'c:\Ico1.ico', IMAGE_ICON, DocumentImgList.Width, DocumentImgList.Height, LR_LOADFROMFILE);
DocumentImgList.AddIcon(IconToAdd);
IconToAdd.Handle := LoadImage(0, 'c:\Ico2.ico', IMAGE_ICON, DocumentImgList.Width, DocumentImgList.Height, LR_LOADFROMFILE);
DocumentImgList.AddIcon(IconToAdd);
finally
IconToAdd.Free;
end;
TImageList raises an "Invalid image size" error under only 2 conditions:
1) The TImageList's Height or Width property is less than 1, or the Height property is greater than 32768, when the TImageList is initially created via the CreateSize() constructor (there are no such limitations imposed by the Height and Width property setters).
2) you try to add/insert a new TBitmap or TIcon whose dimensions do not fit within TImageList's internal image.

TImage losing loaded picture

Running Turbo Delphi Pro.
I'm using TImage to display a png image.
When I restart Delphi and load the dpr file, TImage is still there, but the picture is lost,
requiring a reload of the picture before compiling.
At first I thought it's a path issue, so I loaded the picture from the same directory as the dpr, but it didn't help.
What else can I try?
Try this:
Open your project and your Form with
the PNG.
(Re)Load the PNG image.
Save and close your Form.
With a text editor, load your Form
DFM
I bet you don't have a big binary in
your TImage object, because the PNG content
has not been saved.
Bottom line, you'd have to include it as a resource and load it dynamically at runtime.
I ran into this problem as well with D2006. The solution I used is similar to François'.
I have a TPngImageCollection component that I collect all the images in at design time. You will need to find unit PngImageList off the web. The TPngImageCollection component has the advantage that you can have a collection of PNG images of differing sizes.
At run-time on startup, I assign the TImages from each of the collection members:
Image1.Picture.Assign (ImageCollection.Items [0].PNGImage) ;
Image2.Picture.Assign (ImageCollection.Items [1].PNGImage) ;
Image3.Picture.Assign (ImageCollection.Items [2].PNGImage) ;
etc
Bingo - you can produce your PNG originals with alpha transparency (I use PhotoPlus 6.0 from Serif - free and very capable) and show them in a TImage.

Resources