I have the following simple code to convert clipboard image to bmp and then to png:
if Clipboard.HasFormat(CF_PICTURE) then
begin
bitmap := TBitmap.Create;
png := TPNGImage.Create;
try
bitmap.Assign(Clipboard);
bitmap.SaveToFile(ExtractFilePath(application.ExeName) + '\filename.bmp');
png.Draw(bitmap.Canvas, Rect(0, 0, bitmap.Width, bitmap.Height));
png.SaveToFile(extractfilepath(application.ExeName) + '\filename.png');
finally
bitmap.free;
png.free;
end;
end;
While the conversion to bmp works and I can even open it in mspaint and see its content, the conversion to png fails and I have a blank png image. What am I doing wrong?
You have not set the dimensions (height and width) of the PNG image object. You would need to do that before drawing to it.
Easier however would be a simple assignment:
png.Assign(Bitmap);
Related
I don't know what to do here anymore, so I hope that somebody can help me.
I'm using Delphi 10.4 and Windows 10.
Basically, my problem is that cutting a part of the .png image with transparent background is to slow. I use scanline.
I have one background image (back.bmp) that is drawn on the form. That image can be also a .png (with no transparency) if that can help to solve this.
From the second image (frontsigns.bmp) I cat different parts and need to draw them to that background.
Old version of this program used .bmp as second image (with no transparent background) so that was very fast.
procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
frontsigns : TBitmap;
begin
frontsigns := TBitmap.Create;
frontsigns.LoadFromFile('E:\frontsigns.bmp');
frmMain.Canvas.CopyRect(Rect(0,0,302,869), frontsigns.Canvas, Rect(0, yStartPos, 302, yEndPos)); // yStartPos and yEndPos are variables
end;
This draw part of the second image (303x870 px) on the background in the 0.415 ms. That is OK (probably can't be faster).
Now I need to use a second image with transparent backgrounds, so I use .png. Because I cut and draw different parts of the second image on the background my idea is that I use temp background image and draw part of the .png on that temp image and after that I draw it on the form.
Here is the code.
procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
background, tmpbackground : TBitmap;
frontsigns, CroppedPng : TPngImage;
begin
background := TBitmap.Create;
background.LoadFromFile('E:\back.bmp');
frontsigns := TPngImage.Create;
frontsigns.LoadFromFile('E:\frontsigns.png');
tmpbackground := TBitmap.Create(303, 870);
tmpbackground.Canvas.CopyRect(Rect(0, 0, 302, 869), background.Canvas, Rect(0, 0, 302, 869));
CropPng(frontsigns, 0, yStartPos, 302, yEndPos, CroppedPng); // yStartPos and yEndPos are variables
tmpbackground.Canvas.Draw(0, 0, CroppedPng);
end;
This draw part of the second image (303x870 px) on the background in the 13.5 ms!!!!!!!
Reason is slow scanline I think. I should write here that frontsigns.png has only fully transparent background. There are not any semi-transparent pixels.
Here is my code for cropping .png images.
const
ColorTabMax = 10;
ColorTab : array[0..ColorTabMax-1] of TColor =
(ClBlack, ClMaroon, ClRed, ClWebDarkOrange, ClYellow, ClGreen, ClBlue, ClPurple, ClGray, ClWhite);
procedure CropPng(Source: TPngImage; Left, Top, Width, Height: Integer; out Target : TPngImage);
function ColorToTriple(Color: TColor): TRGBTriple;
begin
Color := ColorToRGB(Color);
Result.rgbtBlue := Color shr 16 and $FF;
Result.rgbtGreen := Color shr 8 and $FF;
Result.rgbtRed := Color and $FF;
end;
var
X, Y : Integer;
Bitmap : TBitmap;
BitmapLine : PRGBLine;
AlphaLineA, AlphaLineB : pngImage.PByteArray;
begin
if (Source.Width < (Left + Width)) or (Source.Height < (Top + Height)) then
raise Exception.Create('Invalid position/size');
Bitmap := TBitmap.Create;
try
Bitmap.Width := Width;
Bitmap.Height := Height;
Bitmap.PixelFormat := pf24bit;
for Y := 0 to Bitmap.Height - 1 do
begin
BitmapLine := Bitmap.Scanline[Y];
for X := 0 to Bitmap.Width - 1 do
BitmapLine^[X] := ColorToTriple(Source.Pixels[Left + X, Top + Y]);
end;
Target := TPngImage.Create;
Target.Assign(Bitmap);
if Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then
begin
Target.CreateAlpha;
for Y := 0 to Target.Height - 1 do
begin
AlphaLineA := Source.AlphaScanline[Top + Y];
AlphaLineB := Target.AlphaScanline[Y];
for X := 0 to Target.Width - 1 do
AlphaLineB^[X] := AlphaLineA^[X + Left];
end;
end;
finally
Bitmap.Free;
end;
end;
I'm open for any ideas here. Can I make scanline works fatser? I don't have semi-transparent pixels so maybe I don't need to do all this.
I've tried with 32bit .bmp images with alpha channel, but haven't made it work with alphablend function.
I'me even open for third party libraries if there is no otehr option.
Thanks.....
In library PngComponents unit PngFunctions offers procedure SlicePNG, which allows to split a TPngImage into separate parts of equal size. As this has to be done only once it may significantly reduce the drawing time.
The problem with your approach is that you are reading your source image by accessing individual pixels using Source.Pixels and not using ScanLine
BitmapLine^[X] := ColorToTriple(Source.Pixels[Left + X, Top + Y]);
If you want to benefit properly by using ScanLine make sure you use ScanLine for both source and target images.
Also since your source and target images are both TPngImage you probably don't even need to create the temporary TBitmap.
And if color palettes of your PNG's match then you don't even need to do any color decoding/encoding but instead just copy data directly from one image to another. Of course you do need to make sure that color palette in your PNG's match each other in advance.
I remember reading about a tool that modifies a PNG's palette information to match with other files some years ago. Unfortunately I don't remember its name. I do remember reading about it in an article about creating of PNG based image atlases for games.
Here is my current progress thank you to the SilverWariors answer. I've just implemented first tip for now.
I was using information from:
https://delphi.cjcsoft.net/viewthread.php?tid=48996
https://en.wikipedia.org/wiki/BMP_file_format
I've replaced:
BitmapLine^[X] := ColorToTriple(Source.Pixels[Left + X, Top + Y]);
with:
BitmapLine^[X] := GetPixel(source, Left + X, Top + Y);
GetPixel function is bellow.
function GetPixel(Source: TPngImage; X, Y: Integer): TRGBTriple;
var
LineSource : pngImage.PByteArray;
begin
LineSource := Source.Scanline[y];
// Get blue value - stored in lowest order byte in a TColor
Result.rgbtBlue := PByteArray(LineSource)^[(x*3)+0];
// Get Green value - second lowest byte in TColor
Result.rgbtGreen := PByteArray(LineSource)^[(x*3)+1];
// Get Red value - third lowest byte in TColor
Result.rgbtRed := PByteArray(LineSource)^[(x*3)+2];
end;
I'm not sure why the color order is like this and not like in the article on the link above. Maybe because .png file is 32bit.
With this change I've decreased time from 13.5 ms to 6.44 ms. That is great, but I think it can be even much better.
Here is where is I see potential improvement.
Now I scan every line two times. One for the RGB colors and one for for ALPA information.
AlphaLineA := Source.AlphaScanline[Top + Y];
I think that I can get ALPHA info from scanline if I scanline returns all four bytes in a 32bit image. I'm I correct?
Maybe something like:
PByteArray(LineSource)^[(x*3)+3];
Another idea is that I can directly write to the final background. Now I cut part of the .png image and draw it on the background at the end. I must use draw because transparency that .png image that I got as result of croping original image will be lost if I use CopyRect.
But If I draw pixels directly to the background (that has ALPHA 255) that would be much faster. Maybe I can avoid that because the background is 32bit .bmp (it can be 32bit .png) without any transparency (ALPHA is 255 for all bits). Also ALPHA for .png that I'm cutting of can be only 255 (not transparent) and 0 (fully transparent).
I'm not sure how I can accomplish this.
I have a TImage into which I loaded a PNG with transparency. David hinted me how to give it a bitmap to draw on:
var
Png: TPngImage;
Bmp: TBitmap;
begin
Png := TPngImage.Create;
Bmp := TBitmap.Create;
try
Png.LoadFromResourceName(HInstance, 'background');
Bmp.Assign(Png);
Image1.Picture.Assign(Bmp);
finally
Png.Free;
Bmp.Free;
end;
with Image1, Canvas do
begin
Pen.Width := 7;
Pen.Color := clBlue;
MoveTo(0, 0);
LineTo(150, 100);
end;
end;
I can draw on the image's canvas, but the color I defined for my Pen is ignored; instead all lines appear gray. I realized that I must be drawing in the alpha channel instead of the RGB channels, which I could confirm by putting another image underneath. (The gray I got is the color of the underlying TForm.)
The clock-face is opaque, and the area around it transparent which allows you to see the cityscape on the image underneath. So instead of having a blue hand on the clock the hand becomes transparent. (I extended the hand to go over the area which was already transparent, but it doesn't seem to change anything there.)
Why am I drawing in the alpha channel, and how can I make Delphi draw in the RGB channels instead?
update
I uploaded a minimal project which should allow you to reproduce the problem here.
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;
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.
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.