Why does rendered content of a TWebBrowser saved as JPEG loses quality? - delphi

I use the procedure below to create a JPG file from a TWebbrowser
That is resulting in a JPG looking OK
Then I load this JPG to a TcxImage control from DevExpress in order to print it. And that messes up my image so it isn't possible to see the map (it is a portion of a map from Google Maps)
The code for loading the image is
imgPrint.Picture.LoadFromFile(lImage);
I don't quite get why this is looking so bad already on screen.
I do it this way in order to be able to print the map.
It could also be done direct from the TWebBrowser but ther I have no control of the output size and adding my own headers and footers are tricky.
procedure TfrmJsZipExplorer.actSaveExecute(Sender: TObject);
var
ViewObject : IViewObject;
r : TRect;
Bitmap: TBitmap;
begin
if WebBrowser1.Document <> nil then
begin
WebBrowser1.Document.QueryInterface(IViewObject, ViewObject) ;
if Assigned(ViewObject) then
try
Bitmap := TBitmap.Create;
try
r := Rect(0, 0, WebBrowser1.Width, WebBrowser1.Height) ;
Bitmap.Height := WebBrowser1.Height;
Bitmap.Width := WebBrowser1.Width;
ViewObject.Draw(DVASPECT_CONTENT, 1, nil, nil, Application.Handle, bitmap.Canvas.Handle, #r, nil, nil, 0);
with TJPEGImage.Create do
try
Assign(Bitmap);
SaveToFile(lImagefile);
finally
Free;
end;
finally
Bitmap.Free;
end;
finally
ViewObject._Release;
end;
end;
end;

How to improve a saved JPEG image quality ?
You may set the CompressionQuality property of your saved image to the lowest compression, but highest image quality value. That will improve visual quality of the output image. Setting this property to 100 will result in better image quality, but larger file size:
with TJPEGImage.Create do
try
Assign(Bitmap);
CompressionQuality := 100;
SaveToFile(lImagefile);
finally
Free;
end;
It is necessary to use JPEG format for this image archive ?
If you're not limited only to JPEG format for you image archive, consider to use a different format, such as PNG. If you'd decide to use a PNG format with TPNGImage class, there's the CompressionLevel property, which allows you to specify the compression level of a saved image and which directly affects the size of the output file, but unlike the JPEG format compression with keeping the same visual quality. Setting this property to 9 will result in full compression to be used, which produces just smaller file size, the quality remains same as if no compression (0 value) would be used:
uses
PNGImage;
with TPNGImage.Create do
try
Assign(Bitmap);
CompressionLevel := 9;
SaveToFile(lImagefile);
finally
Free;
end;

Related

How to reduce the size of JPG file?

Few days ago I asked this question and got the answer:
How to add a picture frame and insert the text in the image?
Now, when I save content from TPanel (1x shape, 1x TImage, 2x TLabel) as JPG file,
size of that JPG file is increased from 20kb, wich is size of picture in TImage, to 620kb.
Dimensions are almost same. Original JPG file 320x320, new JPG picture 361x440.
So, how to reduce that size?
This is answer for first question, from #iPath, so that is how new JPG file is created:
procedure TForm1.SavePanelAsImage;
var
img: TBitmap;
begin
img := TBitmap.Create;
try
img.Width := fpPanel.Width;
img.Height := fpPanel.Height;
fpPanel.PaintTo(img.Canvas, 0, 0);
img.SaveToFile(fpFileName);
finally
img.Free;
end;
end;
What you have saved is not a JPEG image. You have saved a Windows bitmap. That has no compression at all. It happens to have the .jpg extension, but that doesn't make the file itself be a JPEG.
You need to use TJPEGImage to save the file. Control the compression by using the CompressionQuality property. Once you have your image in a bitmap, transfer it to a JPEG
uses
jpeg;
procedure TForm1.SavePanelAsImage;
var
img: TBitmap;
JpegImage: TJPEGImage;
begin
img := TBitmap.Create;
try
img.Width := fpPanel.Width;
img.Height := fpPanel.Height;
fpPanel.PaintTo(img.Canvas, 0, 0);
JpegImage := TJPEGImage.Create;
try
JpegImage.CompressionQuality := ...;//you decide the value
JpegImage.Assign(img);
JpegImage.SaveToFile(fpFileName);
finally
JpegImage.Free;
end;
finally
img.Free;
end;
end;
The file is being saved as a Bitmap, and not as a JPEG.
Even at "extremely good quality", a JPEG will not be that size.
However, a 32-bit Bitmap (without RLE) will be - 361 * 440 * 4 (bytes/pixel) ~ 640k
implementation
uses Jpeg;
procedure SaveBMPasJPG(bmp:TBitmap; const FileName:String;Quality:Integer=90);
var
jpg:TJpegImage;
begin
jpg:=TJpegImage.Create;
try
jpg.CompressionQuality := Quality;
jpg.Assign(bmp);
jpg.SaveToFile(FileName);
finally
jpg.Free;
end;
end;
Try Kernel Bulk image resizer tool for resizing your image single or multiple at once.

How to load a transparent Image from ImageList?

I want to load a picture (32 bit-depth, transparent) from a TImageList to an TImage. The standard approach would be ImageList.GetBitmap(Index, Image.Picture.Bitmap);. However the GetBitmap method doesn't work with transparency, so I always get a non-transparent bitmap.
The workaround is rather simple - ImageList offers another method, GetIcon, which works OK with transparency. Code to load a transparent Image would be:
ImageList.GetIcon(Index, Image.Picture.Icon);
And don't forget to set proper ImageList properties:
ImageList.ColorDepth:=cd32bit;
ImageList.DrawingStyle:=dsTransparent;
I too have had various issues with passing in images from the a tImageList. So I have a simple wrapper routine that generally does the job and it enforces the transparency. The code below is Delphi 2005 and imlActiveView is the tImageList component that has my set of button glyph images.
procedure TfrmForm.LoadBitmap (Number : integer; bmp : tBitMap);
var
ActiveBitmap : TBitMap;
begin
ActiveBitmap := TBitMap.Create;
try
imlActiveView.GetBitmap (Number, ActiveBitmap);
bmp.Transparent := true;
bmp.Height := ActiveBitmap.Height;
bmp.Width := ActiveBitmap.Width;
bmp.Canvas.Draw (0, 0, ActiveBitmap);
finally
ActiveBitmap.Free;
end
end;
Here is an example of use where the 5th imlActiveView image is passed into the btnNavigate.Glyph.
LoadBitmap (5, btnNavigate.Glyph)

CopyRect (scaling) with the correct colors in Delphi

In this question I asked about the correct use of the CopyRect method. I got an answer which fixed my problem, but now the colors of the copied rectangle are wrong (limited to 256 values?).
This is the code:
var
Bmp: TBitmap;
begin
Image1.Picture.LoadFromFile(SomeJPGimage);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
with Bmp do
Image2.Canvas.CopyRect(Image2.Canvas.ClipRect, Canvas, Canvas.ClipRect);
finally
Bmp.Free;
end;
end;
The inset with the false colors is Image2. The colors are right if I don't resize.
How do I get the 24 bit color of the source image (a JPG) when resizing?
edit
Draw is not an alternative; I want to copy a scaled version of part of the source image.
This is not caused because of color reduction, or a wrong pixelformat etc.. You're probably shrinking the image while copying and 'StretchBlt' compresses the image to fit in, and depending on the mode, produces some artifacts. For instance the below 128x128 image    
is displayed exactly the same if no resizing is applied. However if it is applied on a 90x100 image for instance, the output is   .
You can change the stretching mode for a slightly better result:
var
Bmp: TBitmap;
begin
Image1.Picture.LoadFromFile(SomeJPGimage);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
SetStretchBltMode(Image2.Canvas.Handle, HALFTONE); // <- here
with Bmp do
Image2.Canvas.CopyRect(Image2.Canvas.ClipRect, Canvas, Canvas.ClipRect);
finally
Bmp.Free;
end;
end;
For the above source picture the output now becomes:
(Having browsed a little 'graphics.pas', the VCL seems to be using halftone only for 8-bit images. I may be wrong or right in this assessment, but in any case halftone stretching mode has no such constraint.)
For anything better, I believe, you have to use a proper graphics library.
Edited again:
Turns out the issue is going against the WRONG canvas (too easy with TImage if you're not used to it). Tried to save files on my last sample and got a huge file on the one I assigned. So I Started looking into some of the other values and found that you need to work against the Bitmap Canvas...
var
BMP: TBitmap;
MyClipRect: TRect;
begin
if OpenDialog1.Execute then
begin
Image1.Picture.LoadFromFile(OpenDialog1.FileName);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
myClipRect.Left := (Bmp.Width div 2);
myClipRect.Top := (Bmp.Height div 2);
myClipRect.Right := (Bmp.Width);
myClipRect.Bottom := (Bmp.Height);
with Image2.Picture.Bitmap do
begin
Width := Bmp.Width div 2;
Height := Bmp.Height div 2;
Canvas.CopyRect(Canvas.ClipRect, Bmp.Canvas, MyClipRect);
end;
Image2.Picture.SaveToFile('image2.bmp');
finally
Bmp.Free;
end;
end;
end;
Hope that finally got it. Yeesh.

How to check if the PNG image loaded into FMX.TBitmap has an alpha channel?

I'm loading PNG images into FMX.Type.TBitmap in Delphi-XE2 Update3 FireMonkey HD application. How do I check if loaded PNG image has an alpha channel or not?
Currently if I load an image with an alpha channel it has alpha info in Bitmap.Scanline[Y]^[X] in a form of $AABBGGRR. However if I load PNG image without alpha the said record has only $00BBGGRR entries (AA = 0), just like an image with clear alpha. Hence the problem - how to determine if it is RGBA image with the alpha fully transparent or it is a RGB image (in this case I will process it to make the alpha fully opaque). Note: Checking through all pixels is not an option.
FMX TBitmap has no PixelFormat property, nor I could find HasAlpha flag.
You're probably not going to like this.
All bitmaps in FMX are 32-bit, and they are loaded and saved using code from the OS, which is all 32-bit.
So, the real answer is that all bitmaps have an alpha channel.
But, what you really want to know is whether the bitmap uses the alpha channel, and the only way to tell this would be to iterate over every pixel and see if any have an alpha channel which is <> 255.
I would recommmend something like the following (untested):
function TBitmap.IsAlpha(Bitmap: TBitmap): Boolean;
var
I, j: Integer;
Bits: PAlphaColorRecArray;
begin
Bits := PAlphaColorRecArray(StartLine);
for j := 0 to Height - 1 do
for I := 0 to Width - 1 do
begin
if Bits[I + (j * Width)].A <> 255 then
begin
Result := True;
EXIT;
end;
end;
Result := False;
end;
Following function checks if a PNG file has a transparency channel. This is easy, since the main PNG header has a fixed length and the data information block IHDR must be the first occurring block.
function PngHasAlphaLayer(f: String): Boolean;
var
fs: TFileStream;
colorType: Byte;
begin
fs := TFileStream.Create(f, fmOpenRead);
fs.Position := 25;
fs.Read(colorType, 1);
fs.Free;
Result := colorType and (1 shl 2) <> 0;
end;
So it is stored in the 26th byte (or 0x19 as hex), in the 3rd bit.
However, this function does not check for a valid file structure for simplicity reasons. So it should be used after the PNG image has been loaded to TBitmap and then its boolean value for transparency support could be stored e. g. in the Tag property of TImage (or wherever you want).

How to Read Pixels in WIC image

The code below (from this thread: How to use Delphi 2010's new WIC capability on Canon files?) opens a WIC image into a BitMap.
However, if the dynamic range of WIC pixel values is large, this code loses a lot of information, since it has to scale the wide dynamic range into the low range that a Bitmap pixel can accommodate.
procedure TForm116.Button1Click(Sender: TObject);
var
WIC: TWICImage;
begin
WIC := TWICImage.Create;
try
WIC.LoadFromFile('MyFilename.raw');
Image1.Picture.Graphic.Assign(WIC);
finally
WIC.Free;
end;
end;
Can anyone show me sample code that would let me read the pixel values directly from the TWICImage, so I can access the image data without losing information? I need the intensity (gray scale) values of each pixel which perhaps can be calculated from the RGB values if not directly available?
Something like:
var
PixelValue: Integer; // Grayscale
for Row := 0 to WIC.Width do
for Col := 0 to WIC.Height
PixelValue := WIC.GetPixelValue(Row, Col);
You should be able to assign() the TWICImage to a TBitmap, and then access the bitmap's Pixels or ScanLine property to get at the pixel data. Much like you'd do when loading a JPEG.
For example (untested pseudocode)
w := TWICImage.Create();
w.LoadFromFile(...)
b := TBitmap.Create;
b.assign( w);
ptr := b.ScanLine[...]

Resources