How set DPI resolution of a TIFF image with Delphi? - delphi

I use this code to draw a image and save it as TIFF with Delphi 2006:
var Bmp: TBitmap;
MF: TMetaFile;
MetafileCanvas: TMetafileCanvas;
begin
Gdip := TGDIPlusFull.Create('gdiplus.dll');
MF := TMetaFile.Create;
MF.Width := 1000;
MF.Height := 1100;
MetafileCanvas := TMetafileCanvas.Create(MF, 0);
MetafileCanvas.Brush.Color := clRed;
MetafileCanvas.Brush.Style := bsDiagCross;
MetafileCanvas.Ellipse(50, 50, 300 - 50, 200 - 50);
MetafileCanvas.Free;
Bmp := Gdip.DrawAntiAliased(MF);
Image1.Picture.Assign(Bmp);
SynGDIPlus.SaveAs(Bmp, 'c:\test.tif', gptTIF);
Bmp.Free;
MF.Free;
FreeAndNil(GdiP);
end;
NOTE I use free framework fromhttp://www.synopse.info.
The code works very well. However I have a problem. How can I set the TIFF resolution.
My test.tif image have 96 DPI (screen resoltion), but I need of 200 DPI.
Note I cannot want change the image dimensions (width and heght), becuase there are right, I want change only DPI resolution.
I have found many answer about this question but nothing about Delphi.

I've added the following method:
procedure TSynPicture.BitmapSetResolution(DPI: single);
begin
if (fImage<>0) and fAssignedFromBitmap and (DPI<>0) then
Gdip.BitmapSetResolution(fImage,DPI,DPI);
end;
Which will call the corresponding GDI+ API for setting a bitmap resolution.
Then it should be specified when saving:
procedure SaveAs(Graphic: TPersistent; const FileName: TFileName;
Format: TGDIPPictureType; CompressionQuality: integer=80;
MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;
var Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmCreate);
try
SaveAs(Graphic,Stream,Format,CompressionQuality,MaxPixelsForBiggestSide,
BitmapSetResolution);
finally
Stream.Free;
end;
end;
So you could be able to write in your code:
Bmp := Gdip.DrawAntiAliased(MF);
Image1.Picture.Assign(Bmp);
SynGDIPlus.SaveAs(Bmp, 'c:\test.tif', gptTIF, 80, 0, 200); // force 200 DPI
Bmp.Free;
See this commit.

TWICImage class is able to save DPI information for TIF files, but the access to this feature is not apparent at first glance. Just call the SetResolution function of the Handle.
tif := TWICImage.Create;
...
tif.Handle.SetResolution( DPI_X, DPI_Y);

Related

Jpeg to Bmp conversion takes unreasonable amount of time

I have this function that takes 4.2 seconds to convert a jpg to bmp.
Why it takes so long? Can I make if faster?
IrfanView loads and converts the file in only a fraction of that time.
I thought that is spends most of the time in JPG.LoadFromFile. But when I measured the time I was surprised to see it spends most of the time in BMP.Assing(JPG).
function ConvertJPG2BMP(CONST FileName: string): TBitmap;
VAR JPG: TJpegImage;
begin
Result:= NIL;
JPG:= TJpegImage.Create;
TRY
JPG.LoadFromFile(FileName);
if (JPG.Width > 0) AND (JPG.Width < 32768)
AND (JPG.Height> 0) AND (JPG.Height < 32768) then
begin
Result:= TBitmap.Create;
TRY
Result.HandleType:= bmDIB;
// Fuji_FinePix_F550.JPG [3200x1800] [1.44MB]
Result.Assign(JPG); <--- 4 seconds!!
EXCEPT
FreeAndNil(Result);
END;
end;
FINALLY
FreeAndNil(JPG);
end;
end;
Since I wanted to test the slightly older functions once, it is a good opportunity to do this now.
The sources used are here
These have been changed a bit in the code below.
Somewhat adapted source code of OP's function ConvertJPG2BMP() (2512 : ms)
function ConvertJPG2BMP(CONST FileName: string): TBitmap;
VAR
JPG: TJpegImage;
begin
Result:= NIL;
JPG:= TJpegImage.Create;
TRY
JPG.LoadFromFile(FileName);
if (JPG.Width > 0) AND (JPG.Width < 32768)
AND (JPG.Height> 0) AND (JPG.Height < 32768) then
begin
Result:= TBitmap.Create;
TRY
Result.PixelFormat := pf24bit;
Result.Width := JPG.Width;
Result.Height := JPG.Height;
Result.HandleType:= bmDIB;
// 2018-10-17 14.04.23.jpg [2560x1920] [1.66MB]
Result.Assign(JPG);
Result.SaveToFile('F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\JPG2BMP.bmp');
EXCEPT
FreeAndNil(Result);
END;
end;
FINALLY
FreeAndNil(JPG);
end;
end;
The source for the TWICImage usage (296 : ms)
There is another class in Vcl.Graphics? called TWICImage that handles images supported by the Microsoft Imaging Component
Including BMP, GIF, ICO, JPEG, PNG, TIF and Windows Media Photo
procedure LoadImageFromStream(Stream: TStream; Image: TImage);
var
wic: TWICImage;
Bitmap: TBitmap;
begin
Stream.Position := 0;
wic := TWICImage.Create;
try
wic.LoadFromStream(Stream);
Image.Picture.Assign(wic);
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf24bit;
Bitmap.Width := Image.Picture.Width;
Bitmap.Height := Image.Picture.Height;
Bitmap.Canvas.Draw(0, 0, Image.Picture.Graphic);
Bitmap.SaveToFile('F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\TWICImage.bmp');
finally
Bitmap.Free;
end;
finally
wic.Free;
end;
end;
procedure RenderImage(const Filename: string);
var
fs: TFileStream;
begin
fs := TFileStream.Create(Filename, fmOpenRead);
try
LoadImageFromStream(fs, Form1.Image1);
finally
fs.Free;
end;
end;
GetTickCount for all tested routines.
procedure TForm1.Button1Click(Sender: TObject);
var
MyDIB : TBitmap;
loadStr : string;
XStart,Xend : LongWord;
begin
loadStr := 'F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\2018-10-17 14.04.23.jpg';
XStart := GetTickCount;
if RadioGroup1.ItemIndex = 0 then MyDIB := ConvertJPG2BMP(loadStr);// ConvertJPG2BMP()
if RadioGroup1.ItemIndex = 1 then TestBmp(loadStr);
if RadioGroup1.ItemIndex = 2 then RenderImage(loadStr);// TWICImage
if RadioGroup1.ItemIndex = 3 then GetOleGraphic(loadStr);
Xend := GetTickCount;
Label1.Caption := IntToStr(xEnd-XStart) + ' : MS' ;
end;
The generated images are identical to the file size only from the function GetOleGraphic() is a smaller file produced with a worse resolution?
here the source used for the GetOleGraphic()
Here is a compact version of WIC image loader posted by moskito-x above.
Please VOTE HIS answer not mine. My answer here is only to provide the compact version and some details.
{-----------------------------------------------
Uses TWICImage
Advantages:
8+ times faster than Delphi's JPG function
Works with: animated GIF, PNG, JPG
Drawbacks:
Fails with JPEG2K
No EXIF support
Platform dependent
-----------------------------------------------}
function LoadImageWic(CONST FileName: string): TBitmap;
VAR
wic: TWICImage;
begin
wic := TWICImage.Create;
TRY
wic.LoadFromFile(FileName);
Result := TBitmap.Create;
TRY
Result.Assign(wic);
EXCEPT
FreeAndNil(Result);
END;
FINALLY
FreeAndNil(wic);
END;
end;
Just try to decompress the jpeg using our Open Source SynGDIPlus unit.
We found it much faster than the Delphi built-in jpeg.pas unit.
The latest revision can be retrieved from github.
As an alternative, you may try to use our fast Jpeg decoder using SSE2 but it doesn't handle all kind of Jpegs, and it is for Win32 only.

How do I make a bitmap version of a WMF file that is loaded into a TImage.Picture and move that to a TSpeedButton.Glyph

For the sake of a minimal complete question, I have a WMF file loaded into a TImage control on a form. This control contains the property Picture, which is a TPicture type. I am trying to "rasterize" the WMF file that I loaded into the TImage, and store that into a TSpeedButton.Glyph.
What is interesting about this process is I am able to use this technique to create a resolution independent custom control (a button in my case) that will redraw its glyph for any resolution you like.
In real world usage, I would not have a TImage or a TSpeedButton, but this question is fundamentally about the process of moving content from TPicture to a TBitmap.
Here is the relevant semi-working code:
procedure CopyBitmap( Source:TImage; DestSpeedButton:TSpeedButton );
var
bmp: TBitmap;
begin
bmp:=TBitmap.Create;
try
// note: with WMF loaded, Source.Picture.Bitmap.Width and Height are 0.
bmp.Width := Source.Width; // originally I had Source.Picture.Bitmap.Width, which didn't work.
bmp.Height := Source.Height; //because Source.Picture.Bitmap.Height, doesn't work.
bmp.Canvas.Draw(0,0, Source.Picture.Graphic );
DestSpeedButton.Glyph:=bmp;
finally
bmp.Free;
end;
end;
Is this the correct approach? Why does the image invert during copy?
A sample WMF file, the exact file I'm using, is found here.
Thanks David, for suggesting that I draw the background. This works.
Note that in production I would change the code below to use Vcl.GraphUtils helper called ScaleImage as the results are much prettier. See the second code sample.
// Quick and Dirty : No sub-pixel anti-aliasing.
// Also does not modifies Source, so set Source's size before you
// call this.
procedure CopyBitmap( Source:TImage; DestSpeedButton:TSpeedButton );
var
bmp: TBitmap;
begin
bmp:=TBitmap.Create;
try
bmp.SetSize( Source.Width, Source.Height);
bmp.Canvas.Pen.Style := psClear;
bmp.Canvas.Brush.Style := bsSolid;
bmp.Canvas.Brush.Color := clFuchsia;
bmp.Canvas.Rectangle(0,0, Source.Width+1,Source.Height+1 );
bmp.Canvas.Draw(0,0, Source.Picture.Graphic );
bmp.TransparentColor := clFuchsia;
DestSpeedButton.Glyph:=bmp;
finally
bmp.Free;
end;
end;
Alternative that uses more memory, and is using the TPicture type instead of TImage because in real use I don't even have a TImage just a TPicture, also this looks nicer. Note that it is written around some custom control of my own design (or yours) that has some property type TBitmap. You have to substitute your own controls, or change TMyControlWithAGlyph to TSpeedButton if that's what you want to do:
// A Bit Better Looking. Uses Vcl.GraphUtils function ScaleImage
procedure CopyBitmap( Source:TPicture;
Dest:TMyControlWithAGlyph;
DestType:TCopyDestTypeEnum;
AWidth,AHeight:Integer;
DoInvert:Boolean;
TransparentColor:TColor=clFuchsia );
var
bmp,bmpFullSize: TBitmap;
ARect:TRect;
ScaleAmount:Double;
begin
if not Assigned(Source) then
exit;
if not Assigned(Dest) then
exit;
if not Assigned(Source.Graphic) then
exit;
bmp:=TBitmap.Create;
bmpFullSize := TBitmap.Create;
try
bmpFullSize.SetSize( Source.Width, Source.Height );
bmpFullSize.PixelFormat := pf24bit;
bmpFullSize.Canvas.Pen.Style := psClear;
bmpFullSize.Canvas.Brush.Style := bsSolid;
bmpFullSize.Canvas.Brush.Color := TransparentColor;
bmpFullSize.Canvas.Rectangle(0,0, Source.Width+1,Source.Height+1 );
bmpFullSize.Canvas.Draw(0,0, Source.Graphic );
bmp.SetSize( AWidth, AHeight);
bmp.PixelFormat := pf24bit;
// Vcl.GraphiUtil version needs a floating point scale.
ScaleAmount := AWidth / Source.Width;
ScaleImage(bmpFullSize,bmp,ScaleAmount );
// This lets me have a white icon and turn it black if I want to
// or vice versa
if DoInvert then
InvertBitmap(bmp);
if DestType=DestLargeGlyph then
begin
Dest.LargeGlyph := bmp;
end
else
begin
Dest.Glyph:=bmp;
end;
finally
bmp.Free;
bmpFullSize.Free;
end;
end;
The above code also calls this little helper:
function InvertBitmap(ABitmap: TBitmap): TBitmap;
var
x, y: Integer;
ByteArray: PByteArray;
begin
ABitmap.PixelFormat := pf24Bit;
for y := 0 to ABitmap.Height - 1 do
begin
ByteArray := ABitmap.ScanLine[y];
for x := 0 to ABitmap.Width * 3 - 1 do
begin
ByteArray[x] := 255 - ByteArray[x];
end;
end;
Result := ABitmap;
end;

Delphi Image Print

I have an array of TImages each one containing thumbnail of a Image file in a specified directory and their Hint property set to their Image Filename for printing purpose.
all files are located on a remote server in a shared directory (Example: \192.168.1.50\imgscan\12-14-54\ *.jpg).
also each Image has a corresponding TCheckBox that users can check to mark images for printing.
I use the following code for printing (variable images_index holds the number of images in the selected directory)...
procedure PrintSelectedImages;
var
i: integer;
R1, R2: TRect;
Picture: TPicture;
Bitmap: TBitmap;
Total, done: integer;
begin
Total := 0;
done := 0;
for i := 0 to images_index - 1 do
if Checks[i].Checked then
INC(Total);
if Total = 0 then
begin
MessageDlg('No Images Selected!', mtInformation, [mbOK], 0);
Exit;
end;
Printer.BeginDoc;
if PrintDialog1.Execute then
begin
for i := 0 to images_index - 1 do
begin
if Checks[i].Checked then
begin
try
Picture := TPicture.Create;
Picture.LoadFromFile(images[i].Hint);
Bitmap := TBitmap.Create;
try
Bitmap.Width := Picture.Width;
Bitmap.Height := Picture.Height;
Bitmap.Canvas.Draw(0, 0, Picture.Graphic);
R1 := Rect(0, 0, Bitmap.Width, Bitmap.Height);
R2 := Rect(0, 0, Printer.PageWidth, Printer.PageHeight);
Printer.Canvas.CopyRect(R2, Bitmap.Canvas, R1);
INC(done);
finally
Bitmap.Free;
end;
finally
Picture.Free;
end;
if done < Total then
Printer.NewPage;
end; // if
end; // i
end; // if
Printer.EndDoc;
end;
Now...
On Microsoft XPS Document Writer I have no problems and all the pages are printed fine, but on real printers most of the time white papers come out and sometimes only some of the selected images are printed (for example 4 of 10 selected files).
What is the problem with my code? i googled a lot and found nothing!
Thanks.
The Canvas CopyRect function uses StretchBLT. We have had better results using the DIBits functions SetDIBitsToDevice or StretchDIBits. Here is our draw code. We have a DrawParams struct that is passed in with the details on how this image should be drawn.
The code below is using a TBitmap32 from graphics32. We use that because of some other drawing and resize routines we find useful. But the same code will work with a normal TBitmap.
{ TDrawParamsRecord }
TDrawParamsRecord = record
private
function GetHeight(): integer;
function GetWidth(): integer;
public
PictureZoom: integer;
Stretch: boolean;
Center: boolean;
KeepAspectRatio: boolean;
OutputRect: TRect;
ResizeMode: TResizeMode;
property Height: integer read GetHeight;
property Width: integer read GetWidth;
function Equal(OtherParams: TDrawParamsRecord): boolean;
end;
{
TCFImage.OutputToCanvas
---------------------------------------------------------------------------
When writing to the canvas we could have a Screen canvas, a metafile canvas
used to create a PDF file, or a printer canvas. Because of this we want to
make sure we are using the DIBits functions. Many printer drivers can't use
the StretchBLT function because of color space changes. Everyone should
support StretchDIBits.
When resizing the image we sometimes will resize it internally to match the
output size and other times we will let StretchDIBits handle the conversion.
}
procedure TCFImage.OutputToCanvas(Canvas: TCanvas; Image: TBitmap32; DrawParams: TDrawParamsRecord);
var
// StretchDIBits has BmpInfo passed in as a Var parameter so we can't
// use the read only property.
BmpInfo: TBitmapInfo;
begin
BmpInfo := Image.BitmapInfo;
// If th output matches the current image size then we can just move the bits,
// no reason for "Stretch"
if (DrawParams.Height = Image.Height) and (DrawParams.Width = Image.Width) then
begin
SetDIBitsToDevice(Canvas.Handle,
DrawParams.OutputRect.Left, DrawParams.OutputRect.Top,
DrawParams.Width, DrawParams.Height,
0, 0, 0, Image.Height, Image.Bits, BmpInfo, DIB_RGB_COLORS);
end
else
begin
StretchDIBits(Canvas.Handle,
DrawParams.OutputRect.Left, DrawParams.OutputRect.Top,
DrawParams.Width, DrawParams.Height,
0, 0, Image.Width, Image.Height,
Image.Bits, BmpInfo, DIB_RGB_COLORS, SRCCOPY);
end;
end;

png to bmp conversion (maintaining transparency)

I am using delphi XE-5 and I am loading button information from a JSON file, in order to create buttons on a TMS ADVToolBar control. Each button is 50X35 and in png format with transparency.
I am getting each url, using the idHTTP component to retrieve it to a stream and then load it into a png. I then draw it onto a transparent BMP. However, I dont think this is the correct way. Anyway, the bmp is then added to a TImageList where it is assigned to a button using the index. The Image shows up on the button, but with no transparency.
see my code below:
imgUrl:= //code to get img url from JSON file;
MS := TMemoryStream.Create;
png := TPngImage.Create;
png.Transparent:= True;
try
idHTTP1.get(imgUrl,MS);
Ms.Seek(0,soFromBeginning);
png.LoadFromStream(MS);
bmp:= TBitmap.Create;
bmp.Transparent:= True;
bmp.Width:= 50;
bmp.Height:= 50;
png.Draw(bmp.Canvas, Rect(7, 7, png.Width, png.Height));
ImageList1.Add(bmp, nil);
AdvGlowBtn.Images:= ImageList1;
AdvGlowBtn.Layout:= blGlyphTop;
AdvGlowBtn.WordWrap:= False;
AdvGlowBtn.AutoSize:= True;
AdvGlowBtn.ImageIndex:= ImageList1.Count-1;
bmp.Free;
finally
FreeAndNil(png);
FreeAndNil(MS);
end;
At first you have to enable the runtime themes (Project Manager) otherwise you will have no transparency of your images.
And this is the code to load the PNG image into your ImageList1
bmp := TBitmap.Create;
try
// everything done before to bmp has no effect
bmp.Assign( png );
// if for some reason the loaded image is smaller
// set the size to avoid the invalid image size error
bmp.Width := ImageList1.Width;
bmp.Height := ImageList1.Height;
AdvGlowBtn.Images:= ImageList1;
...
// now add the Bitmap to the ImageList
AdvGlowBtn.ImageIndex := ImageList1.Add( bmp, nil );
finally
bmp.Free;
end;
I have an old project in Delphi 5 and I still using it sometimes.
This is my solution using the png object.
procedure ImageList2Alpha(const ImageList: TImageList);
const
Mask: array[Boolean] of Longint = (0, ILC_MASK);
var
TempList: TImageList;
begin
if Assigned(ImageList) then
begin
TempList := TImageList.Create(nil);
try
TempList.Assign(ImageList);
with ImageList do
begin
Handle := ImageList_Create(Width, Height, ILC_COLOR32 or Mask[Masked], 0, AllocBy);
if not HandleAllocated then
raise EInvalidOperation.Create(SInvalidImageList);
end;
Imagelist.AddImages(TempList);
finally
FreeAndNil(TempList);
end;
end;
end;
procedure LoadPngToBmp(var Dest: TBitmap; AFilename: TFilename);
type
TRGB32 = packed record
B, G, R, A : Byte;
end;
PRGBArray32 = ^TRGBArray32;
TRGBArray32 = array[0..0] of TRGB32;
type
TRG24 = packed record
rgbtBlue, rgbtGreen, rgbtRed : Byte;
end;
PRGBArray24 = ^TPRGBArray24;
TPRGBArray24 = array[0..0] of TRG24;
type
TByteArray = Array[Word] of Byte;
PByteArray = ^TByteArray;
TPByteArray = array[0..0] of TByteArray;
var
BMP : TBitmap;
PNG: TPNGObject;
x, y: Integer;
BmpRow: PRGBArray32;
PngRow : PRGBArray24;
AlphaRow: PByteArray;
begin
Bmp := TBitmap.Create;
PNG := TPNGObject.Create;
try
if AFilename <> '' then
begin
PNG.LoadFromFile(AFilename);
BMP.PixelFormat := pf32bit;
BMP.Height := PNG.Height;
BMP.Width := PNG.Width;
if ( PNG.TransparencyMode = ptmPartial ) then
begin
for Y := 0 to BMP.Height-1 do
begin
BmpRow := PRGBArray32(BMP.ScanLine[Y]);
PngRow := PRGBArray24(PNG.ScanLine[Y]);
AlphaRow := PByteArray(PNG.AlphaScanline[Y]);
for X := 0 to BMP.Width - 1 do
begin
with BmpRow[X] do
begin
with PngRow[X] do
begin
R := rgbtRed; G := rgbtGreen; B := rgbtBlue;
end;
A := Byte(AlphaRow[X]);
end;
end;
end;
end else
begin
for Y := 0 to BMP.Height-1 do
begin
BmpRow := PRGBArray32(BMP.ScanLine[Y]);
PngRow := PRGBArray24(PNG.ScanLine[Y]);
for X := 0 to BMP.Width - 1 do
begin
with BmpRow[X] do
begin
with PngRow[X] do
begin
R := rgbtRed; G := rgbtGreen; B := rgbtBlue;
end;
A := 255;
end;
end;
end;
end;
Dest.Assign(BMP);
end;
finally
Bmp.Free;
PNG.Free;
end;
end;
Call ImageList2Alpha(YourImageList) on the OnCreate of the Form (FormCreate), and the ImageList will be ready to store your Bitmaps32 keeping the transparency.
Call the LoadPngToBmp procedure to convert a PNG to Bitmap32 and then, store it on your ImageList.
The TBitmap class uses Windows own libraries to manipulate Bitmaps. Depending on you Windows version, the underlying Operating System libraries does not support 32 bits BMPs, despite the libraries header files declares a BITMAPQUAD struct.
For newer versions of Windows (Vista and above afaik), the field BITMAPQUAD.reserved is used to store the alpha channel. For older versions, this field must remain zero (0x00).
If you are using a "recent" version of Windows, the only possible explanation I see is that the TBitmap class were not updated to support the alpha channel.
Using the class TPNGImage should not be an issue instead of converting it to BMP before using, unless you have some more specific needs.
Use it like that:
ABitmap.SetSize(png.Width, png.Height);
png.AssignTo(ABitmap);

Convert emf to bmp

How can convert emf to bmp with delphi 2010?
If you want to draw the EMF with Anti-Aliaising, you can use our freeware SynGdiPlus library:
Gdip := TGDIPlusFull.Create;
MF := TMetaFile.Create;
MF.LoadFromFile(Files[Tag]);
Bmp := Gdip.DrawAntiAliased(MF,100,100); // 100% zoom in both axis
img1.Picture.Assign(Bmp);
The drawing is done using GDI+, so the rendering will be much better than the direct Canvas.Draw direct method.
You could try to use basis anti-aliaising by stretching the bitmap to a smaller size, but in this case, the font rendering will be alterated. Our native GDI+ drawing produces better rendering quality.
See http://synopse.info/forum/viewtopic.php?id=10
Use this code
procedure ConvertEMF2BMP(EMFFileName, BMPFileName: String) ;
var
MetaFile : TMetafile;
Bitmap : TBitmap;
begin
Metafile := TMetaFile.Create;
Bitmap := TBitmap.Create;
try
MetaFile.LoadFromFile(EMFFileName) ;
with Bitmap do
begin
Height := Metafile.Height;
Width := Metafile.Width;
Canvas.Draw(0, 0, MetaFile) ;
SaveToFile(BMPFileName) ;
end;
finally
Bitmap.Free;
MetaFile.Free;
end;
end;
Try something like:
var
bmp: TBitmap;
wmf: TMetafile;
bmp.SetSize(wmf.Width, wmf.Height);
bmp.Canvas.Draw(0, 0, wmf);

Resources