I have been trying to convert bitmaps to compressed jpegs to save space in a database, but so far with no success. I use Delphi 11.1 with FMX.
My code looks as follows:
var
NewBitmap: TBitmap;
CodecParams : TBitmapCodecSaveParams;
MS1 : TMemoryStream;
Surf: TBitmapSurface;
JpgQuality : TBitmapCodecSaveParams;
begin
...
JpgQuality.Quality := 100;
MS1.Position := 0;
Surf := TBitmapSurface.create;
try
Surf.assign(NewBitmap);
// use the codec to save Surface to stream
if not TBitmapCodecManager.SaveToStream(
MS1,
Surf,
// '.jpg', JpgQuality) then // THIS DOES NOT WORK
'.jpg') then // THIS DOES WORK BUT NO COMPRESSION (FORMAT MAY NOT EVEN BE JPEG)
raise EBitmapSavingFailed.Create(
'Error saving Bitmap to jpg');
finally
Surf.Free;
end;
...
end;
If you check the function :
class function SaveToStream(const AStream: TStream; const ABitmap: TBitmapSurface; const AExtension: string; const ASaveParams: PBitmapCodecSaveParams = nil): Boolean; overload;
You can see that ASaveParams is type of PBitmapCodecSaveParams :
PBitmapCodecSaveParams = ^TBitmapCodecSaveParams;
As mentionned by AmigoJack you need to use a pointer :
var
NewBitmap: TBitmap;
MS1 : TMemoryStream;
Surf: TBitmapSurface;
JpgQuality : TBitmapCodecSaveParams;
begin
NewBitmap := TBitmap.CreateFromFile('input.bmp');
MS1 := TMemoryStream.Create;
Surf := TBitmapSurface.create;
try
MS1.Position := 0;
Surf.Assign(NewBitmap);
JpgQuality.Quality := 100;
if not TBitmapCodecManager.SaveToStream(MS1, Surf, '.jpg', #JpgQuality) then
raise EBitmapSavingFailed.Create('Error saving Bitmap to jpg');
MS1.SaveToFile('ouput.jpg');
finally
NewBitmap.Free;
MS1.Free;
Surf.Free;
end;
end;
Related
I'm trying to change the Exif tag "Orientation" (0x0112) for a given image by code.
Here I've found a working example about reading but I'm failing in writing the same tag.
uses
GDIPAPI, GDIPOBJ, GDIPUTIL;
var
GPImage: TGPImage;
BufferSize: Cardinal;
Orientation: Byte;
RotateType: TRotateFlipType;
EncoderClsid: TGUID;
PI : PropertyItem;
begin
GPImage := TGPImage.Create('.\test_up.jpg');
try
BufferSize := GPImage.GetPropertyItemSize(PropertyTagOrientation);
if BufferSize <= 0
then raise Exception.Create('BufferSize <= 0');
Orientation := 6; //this should be Rotate90FlipNone
PI.id := PropertyTagOrientation;
PI.type_ := 3;
PI.length := BufferSize;
PI.value := PByte(Orientation);
GPImage.SetPropertyItem(PI);
GetEncoderClsid('image/jpeg', EncoderClsid);
GPImage.Save('.\test_up_Rotate90FlipNone.jpg', EncoderClsid);
finally
GPImage.Free
end;
end;
At runtime it raises the following EAccessViolation at the GPImage.SetPropertyItem(PI); line:
Access violation at address 757A8E30 in module 'msvcrt.dll'. Read of
address 00000006.
This is my test_up.jpg:
I am successfully using this code:
procedure TOvbCustomImage.SetImageOrientation(AGPImage: TGPImage; Value: WORD);
var
PropItem : TPropertyItem;
begin
if not Assigned(AGPImage) then
Exit;
PropItem.Id := PropertyTagOrientation;
PropItem.Length := SizeOf(WORD);
PropItem.Type_ := PropertyTagTypeShort;
PropItem.Value := #Value;
AGPImage.SetPropertyItem(PropItem);
end;
This is a function I wrote in my application. Full source code at GitHub.
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.
I would like to convert a TBitMap to a PBitMap in KOL.
I tried this but I get a black picture as an output:
function TbitMapToPBitMap (bitmap : TBitMap) : PbitMap;
begin
result := NIL;
if Assigned(bitmap) then begin
result := NewBitmap(bitmap.Width, bitmap.Height);
result.Draw(bitmap.Canvas.Handle, bitmap.Width, bitmap.Height);
end;
end;
Any idea what's wrong with it? I am using Delphi7.
Thank you for your help.
EDIT: New CODE:
function TbitMapToPBitMap (const src : TBitMap; var dest : PBitMap) : Bool;
begin
result := false;
if (( Assigned(src) ) and ( Assigned (dest) )) then begin
dest.Draw(src.Canvas.Handle, src.Width, src.Height);
result := true;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TBitMapTest : TBitMap;
PBitMapTest : PBitMap;
begin
TBitMapTest := TBitMap.Create;
TBitMapTest.LoadFromFile ('C:\test.bmp');
PBitMapTest := NewBitMap (TBitMapTest.Width, TBitMapTest.Height);
TbitMapToPBitMap (TBitMapTest, PBitMapTest);
PBitMapTest.SaveToFile ('C:\test2.bmp');
PBitMapTest.Free;
TBitMapTest.Free;
end;
To answer your question why are your target images black; it's because you were drawing those target images to source and black they were because the NewBitmap initializes images to black.
How to copy or convert if you want a TBitmap to KOL PBitmap I found only one way (maybe I missed such function in KOL, but even if so, the method used in the following code is very efficient). You can use the Windows GDI function for bit-block transfer, the BitBlt, which just copies the specified area from one canvas to another.
The following code, when you click on the button creates the VCL and KOL bitmap instances, loads the image to a VCL bitmap, call the VCL to KOL bitmap copy function and if this function succeed, draw the KOL bitmap to the form canvas and free both bitmap instances:
uses
Graphics, KOL;
function CopyBitmapToKOL(Source: Graphics.TBitmap; Target: PBitmap): Boolean;
begin
Result := False;
if Assigned(Source) and Assigned(Target) then
begin
Result := BitBlt(Target.Canvas.Handle, 0, 0, Source.Width, Source.Height,
Source.Canvas.Handle, 0, 0, SRCCOPY);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
KOLBitmap: PBitmap;
VCLBitmap: Graphics.TBitmap;
begin
VCLBitmap := Graphics.TBitmap.Create;
try
VCLBitmap.LoadFromFile('d:\CGLIn.bmp');
KOLBitmap := NewBitmap(VCLBitmap.Width, VCLBitmap.Height);
try
if CopyBitmapToKOL(VCLBitmap, KOLBitmap) then
KOLBitmap.Draw(Canvas.Handle, 0, 0);
finally
KOLBitmap.Free;
end;
finally
VCLBitmap.Free;
end;
end;
I'm using Delphitwain (delphitwain.sourceforge.net) to add scan functionality to my app. Scanning is working fine and I can save bmp and jpeg files. Now I need to:
save in 300dpi (the scanner is capable)
save in TIFF format
After digging around, I found 2 tips:
http://www.delphipraxis.net/132787-farbstich-nach-bitmap-operation.html
http://synopse.info/fossil/wiki?name=GDI%2B
Here is my final code:
procedure TForm1.GoAcquireClick(Sender: TObject);
begin
Counter := 0;
Twain.SourceManagerLoaded := TRUE;
Twain.LoadSourceManager;
Twain.TransferMode := ttmMemory;
with Twain.Source[ 0 ] do
begin
Loaded := TRUE;
SetIXResolution(300);
SetIYResolution(300);
SetIBitDepth(1);
EnableSource(true, true);
while Enabled do Application.ProcessMessages;
end;
end;
procedure TForm1.TwainTwainAcquire(Sender: TObject; const Index: Integer;
Image: TBitmap; var Cancel: Boolean);
var
TiffHolder: TSynPicture;
begin
Inc( Counter );
Current := Counter;
ImageHolder.Picture.Assign( Image );
ImageHolder.Picture.Bitmap.Monochrome := true;
ImageHolder.Picture.Bitmap.Pixelformat := pf1Bit;
SynGDIPlus.SaveAs(ImageHolder.Picture, format('c:\temp\teste%d.tif',[ Counter ]), gptTIF );
end;
Result: the image still is 96dpi and were saved as BMP (even with TIF extension).
What am I missing?
The GDI+ library is able to save tiff pictures.
The SynGdiPlus unit use {557CF405-1A04-11D3-9A73-0000F81EF32E} for its TIFF encoder.
From the TSynPicture.SaveAs code, I see two possibilities:
Either you don't have the corresponding TIFF encoder installed;
Either there is some missing parameter expected by the TIFF encoder.
Try this version:
type
/// the optional TIFF compression levels
// - use e.g. ord(evCompressionCCITT4) to save a TIFF picture as CCITT4
TGDIPPEncoderValue = (
evColorTypeCMYK,
evColorTypeYCCK,
evCompressionLZW,
evCompressionCCITT3,
evCompressionCCITT4,
evCompressionRle,
evCompressionNone,
(...)
evFrameDimensionPage);
const
EncoderCompression: TGUID = '{e09d739d-ccd4-44ee-8eba-3fbf8be4fc58}';
function TSynPicture.SaveAs(Stream: TStream; Format: TGDIPPictureType;
CompressionQuality: integer): TGdipStatus;
var fStream: IStream;
Len,Dummy: Int64;
tmp: pointer;
Params: TEncoderParameters;
PParams: pointer;
MS: TMemoryStream absolute Stream;
begin
if not Gdip.Exists or (Stream=nil) or (fImage=0) then begin
result := stInvalidParameter;
exit;
end;
Params.Count := 1;
Params.Parameter[0].Type_ := EncoderParameterValueTypeLong;
Params.Parameter[0].NumberOfValues := 1;
Params.Parameter[0].Value := #CompressionQuality;
PParams := nil;
case Format of
gptJPG: if CompressionQuality>=0 then begin
Params.Parameter[0].Guid := EncoderQuality;
PParams := #Params;
end;
gptTIF: begin
if not (TGDIPPEncoderValue(CompressionQuality) in [
evCompressionLZW, evCompressionCCITT3, evCompressionCCITT4,
evCompressionRle, evCompressionNone]) then
// default tiff compression is LZW
CompressionQuality := ord(evCompressionLZW);
Params.Parameter[0].Guid := EncoderCompression;
PParams := #Params;
end;
end;
CreateStreamOnHGlobal(0, true, fStream);
(...)
It will add the EncoderCompression parameter for TIFF pictures, which seems to be required.
I've updated the source code repository version to include this correction.
Saving TIFF files is hard in Delphi. I don't know any free/open source modules that do this.
ImageEn works.
In the past I have used saving as bmp and converting to tiff with irfanview through createprocess with the commandline
'i_view32.exe c:\temp\scanned.bmp /bpp=1 /convert=c:\temp\scanned.tif'
I'm kindly asking you to help me with this problem:
There's a byte array (data: PByte) containing DIB data AND DIBHeader:
TDibHeader = record
size: Cardinal;
width: Integer;
height: Integer;
planes: Word;
bits: Word;
compression: Cardinal;
image_size: Cardinal;
x_res: Integer;
y_res: Integer;
n_colors: Cardinal;
important_colors: Cardinal;
end;
How to convert DIB to TBitmap while keeping the CPU usage low ?
I've tried http://files.codes-sources.com/fichier.aspx?id=43989&f=GdipApi.pas with no success.
I've assigned DIB to an Memory Stream:
DibMemStream.Clear;
DibMemStream.SetSize(header.image_size);
MoveMemory(DibMemStream.Memory,DibBuffer,header.image_size);
I suppose there should be DIB header written somewhere before Bitmap.LoadFromMemoryStream(DibMemStream). Not sure where.
Any ideas please ?
Thank you !
I have used the following scheme to convert in-memory images to TBitmap:
1) Fill TBMPHeader structure
TBMPHeader = packed record
bmfHeader: TBitmapFileHeader;
bmiHeader: TBitmapInfoHeader;
bmiColors: {depends on image format, may be absent};
end;
2) Write BMPHeader + Image Data to MemoryStream
3) Load TBitmap from MemoryStream using TBitmap.LoadFromStream
You seems to have bmiHeader structure filled already. Add bmfHeader and (maybe) bmiColors.
Here is the code I used to convert 256-color grayscale in-memory images to TBitmap (many years ago, sorry, so no details):
procedure TksImage.CopyToBitmap(Bitmap: TBitmap);
var
Stream: TStream;
begin
Stream:= TMemoryStream.Create;
try
SaveToStream(Stream);
Stream.Position:= 0;
Bitmap.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TksImage.SaveToStream(Stream: TStream);
type
TBMPHeader = packed record
bmfHeader: TBitmapFileHeader;
bmiHeader: TBitmapInfoHeader;
bmiColors: array[0..255] of TRGBQuad;
end;
var
BMPHeader: TBMPHeader;
N: LongWord;
I: Integer;
begin
FillChar(BMPHeader, SizeOf(BMPHeader), 0);
with BMPHeader.bmfHeader do begin
bfType:= $4D42; {'BM'}
bfOffBits:= SizeOf(BMPHeader);
if FChannels = 4 then Dec(bfOffBits, SizeOf(BMPHeader.bmiColors));
bfSize:= bfOffBits + LongWord(FImageSize);
end;
with BMPHeader.bmiHeader do begin
biSize:= SizeOf(BMPHeader.bmiHeader);
biWidth:= FWidth;
biHeight:= FHeight;
biPlanes:= 1;
biBitCount:= 8 * FChannels;
biCompression:= BI_RGB;
biSizeImage:= FImageSize;
{((((biWidth * biBitCount) + 31) and not 31) shr 3) * biHeight;}
end;
N:= 0;
for I:= 0 to 255 do begin
LongWord(bmpHeader.bmiColors[I]):= N;
Inc(N, $010101);
end;
Stream.Write(BMPHeader, BMPHeader.bmfHeader.bfOffBits);
Stream.Write(FImageData^, FImageSize);
end;
It's been a long time since I did any Delphi coding and I've not been able to test this, but if you can provide a handle to the DIB, there's a function - hDIBToTBitmap1() - that should do the trick in this link:
http://www.efg2.com/Lab/Library/Delphi/Graphics/LeadToolsConversions.TXT