Once you load and image into a component, I can see that Delphi store the image data on DFM, . Example:
object img1: TImage
Left = 71
Top = 2
Width = 18
Height = 18
Picture.Data = {
0954506E67496D61676589504E470D0A1A0A0000000D49484452000000100000
001008060000001FF3FF610000000473424954080808087C0864880000000970
485973000000750000007501E3C207650000001974455874536F667477617265
007777772E696E6B73636170652E6F72679BEE3C1A000000EF4944415478DAAD
923B0AC2401086B3D7100F6110B415FBD8AB60E323E62262632DF15158A8BDB6
22D682E821C41B58C76F7003EB8A9A10073E36ECFCFB6766765514458E842258
3A5083A2F38C136C6016C5422B94EC7336C7F7122A7081A3CE97A0000768A2BD
BD1968F6E0428068FD2250AACE32863354ED4AE4701726D0B00F5B262BE8A199
DA065BC893709D2F8189547045E7D906D2D79684FFC32064F1D0E5FE6E90B985
CC434C738DF2F7BB7995691E521F163A1FC4262AE15396AA7650D6FBD2862F26
EAC313B767A0741BE64DCD657E890C0C93500F3D8E616203C344CA6FEBAD5B2A
03C364002D183D00658D8FCCCDEDEA100000000049454E44AE426082}
end
As you can see it's an small image. How it's possible to get this information without the DFM and make it an Image, for example:
procedure TForm12.btn2Click(Sender: TObject);
var
img2: TImage;
Loutput: TStream;
begin
ObjectTextToBinary(TStringStream.Create(
'0954506E67496D61676589504E470D0A1A0A0000000D49484452000000100000'
+ '001008060000001FF3FF610000000473424954080808087C0864880000000970'
+ '485973000000750000007501E3C207650000001974455874536F667477617265'
+ '007777772E696E6B73636170652E6F72679BEE3C1A000000EF4944415478DAAD'
+ '923B0AC2401086B3D7100F6110B415FBD8AB60E323E62262632DF15158A8BDB6'
+ '22D682E821C41B58C76F7003EB8A9A10073E36ECFCFB6766765514458E842258'
+ '3A5083A2F38C136C6016C5422B94EC7336C7F7122A7081A3CE97A0000768A2BD'
+ 'BD1968F6E0428068FD2250AACE32863354ED4AE4701726D0B00F5B262BE8A199'
+ 'DA065BC893709D2F8189547045E7D906D2D79684FFC32064F1D0E5FE6E90B985'
+ 'CC434C738DF2F7BB7995691E521F163A1FC4262AE15396AA7650D6FBD2862F26'
+ 'EAC313B767A0741BE64DCD657E890C0C93500F3D8E616203C344CA6FEBAD5B2A'
+ '03C364002D183D00658D8FCCCDEDEA100000000049454E44AE426082'), Loutput);
img2 := TImage.Create(self);
img2.Name := 'image2';
img2.Left := 71;
img2.Top := 30;
img2.Width := 18;
img2.Height := 18;
img2.Picture.Graphic.LoadFromStream(Loutput);
img1.Parent := Self;
end;
You cannot access the TPicture.Graphic property until an image has been loaded into the TPicture first.
TPicture does not support loading data from a TStream (see QC #12434: Add LoadFromStream() method to TPicture), so you will have to stream the image data manually.
The Picture.Data property data starts with a UTF-8 encoded ShortString containing the name of the TGraphic-derived class that produced the image data. In your example, that class name is encoded as:
0954506E67496D616765
The first byte (hex 09) is the number of bytes in the class name (9), the following 9 bytes (hex 54 50 6E 67 49 6D 61 67 65) are the UTF-8 octets of the class name (TPngImage), and the remaining stream bytes are the actual PNG image data.
So, you need to:
extract the class name from the stream.
instantiate the specified TGraphic-derived class type.
load the remaining stream into the object.
assign the object to TPicture.
For example:
uses
System.Classes,
System.SysUtils,
Vcl.Graphics,
Vcl.Imaging.Jpeg,
Vcl.Imaging.GIFImg,
Vcl.Imaging.PngImage;
type
TGraphicAccess = class(TGraphic)
end;
procedure TForm12.btn2Click(Sender: TObject);
var
Linput: String;
Loutput: TMemoryStream;
LclsName: ShortString;
Lgraphic: TGraphic;
img2: TImage;
begin
Linput := '0954506E67496D61676589504E470D0A1A0A0000000D49484452000000100000'
+ '001008060000001FF3FF610000000473424954080808087C0864880000000970'
+ '485973000000750000007501E3C207650000001974455874536F667477617265'
+ '007777772E696E6B73636170652E6F72679BEE3C1A000000EF4944415478DAAD'
+ '923B0AC2401086B3D7100F6110B415FBD8AB60E323E62262632DF15158A8BDB6'
+ '22D682E821C41B58C76F7003EB8A9A10073E36ECFCFB6766765514458E842258'
+ '3A5083A2F38C136C6016C5422B94EC7336C7F7122A7081A3CE97A0000768A2BD'
+ 'BD1968F6E0428068FD2250AACE32863354ED4AE4701726D0B00F5B262BE8A199'
+ 'DA065BC893709D2F8189547045E7D906D2D79684FFC32064F1D0E5FE6E90B985'
+ 'CC434C738DF2F7BB7995691E521F163A1FC4262AE15396AA7650D6FBD2862F26'
+ 'EAC313B767A0741BE64DCD657E890C0C93500F3D8E616203C344CA6FEBAD5B2A'
+ '03C364002D183D00658D8FCCCDEDEA100000000049454E44AE426082';
Loutput := TMemoryStream.Create;
try
Loutput.Size := Length(Linput) div 2;
HexToBin(PChar(Linput), Loutput.Memory^, Loutput.Size);
LclsName := PShortString(Loutput.Memory)^;
Lgraphic := TGraphicClass(FindClass(UTF8Decode(LclsName))).Create;
try
Loutput.Position := 1 + Length(LclsName);
TGraphicAccess(Lgraphic).ReadData(Loutput);
img2 := TImage.Create(self);
img2.Parent := Self;
img2.Name := 'image2';
img2.Left := 71;
img2.Top := 30;
img2.Width := 18;
img2.Height := 18;
img2.Picture.Assign(Lgraphic);
finally
Lgraphic.Free;
end;
finally
Loutput.Free;
end;
end;
initialization
// this is not necessary for TPicture's own DFM streaming,
// but it is necessary for manual streaming, unless you
// implement your own classname lookups...
//
RegisterClass(TMetafile);
RegisterClass(TIcon);
RegisterClass(TBitmap);
RegisterClass(TWICImage);
RegisterClass(TJpegImage);
RegisterClass(TGifImage);
RegisterClass(TPngImage);
// and so on...
end.
procedure TForm1.FormCreate(Sender: TObject);
const
CONST_SIGN = '0954506E67496D616765';
var
LString: String;
LStart: Integer;
LStringStream: TStringStream;
LMem: TMemoryStream;
R: TBytes;
begin
LString :=
'0954506E67496D61676589504E470D0A1A0A0000000D49484452000000100000'
+ '001008060000001FF3FF610000000473424954080808087C0864880000000970'
+ '485973000000750000007501E3C207650000001974455874536F667477617265'
+ '007777772E696E6B73636170652E6F72679BEE3C1A000000EF4944415478DAAD'
+ '923B0AC2401086B3D7100F6110B415FBD8AB60E323E62262632DF15158A8BDB6'
+ '22D682E821C41B58C76F7003EB8A9A10073E36ECFCFB6766765514458E842258'
+ '3A5083A2F38C136C6016C5422B94EC7336C7F7122A7081A3CE97A0000768A2BD'
+ 'BD1968F6E0428068FD2250AACE32863354ED4AE4701726D0B00F5B262BE8A199'
+ 'DA065BC893709D2F8189547045E7D906D2D79684FFC32064F1D0E5FE6E90B985'
+ 'CC434C738DF2F7BB7995691E521F163A1FC4262AE15396AA7650D6FBD2862F26'
+ 'EAC313B767A0741BE64DCD657E890C0C93500F3D8E616203C344CA6FEBAD5B2A'
+ '03C364002D183D00658D8FCCCDEDEA100000000049454E44AE426082';
{ Find and rid signature }
LStart := Pos(CONST_SIGN, LString);
if LStart = 0 then
Exit;
Delete(LString, LStart, Length(CONST_SIGN));
{ Main }
LStringStream := TStringStream.Create(LString);
LMem := TMemoryStream.Create;
try
{ Prepare out array }
SetLength(R, Length(LString) div SizeOf(Char));
{ Convert }
HexToBin(PWideChar(LString), R, Length(LString) div SizeOf(Char));
{ Copy array to stream }
LMem.WriteBuffer(R[0], Length(R));
{ Save stream with image as file }
LMem.SaveToFile('xxx.png');
{ Load image from file }
Image1.Picture.LoadFromFile('xxx.png');
finally
LStringStream.Free;
LMem.Free;
end;
end;
I had cause to do this recently, later version of Delphi (not sure what version it was introduced) has the LoadFromStream function so the functions I created are as follows.
Function ImageToHex(Image:Timage; LineLen:integer):Tstringlist;
var ms:TmemoryStream; s:String; t:Ansistring;
begin
ms:=tmemorystream.Create;
try
image.Picture.SaveToStream(ms);
setlength(t,ms.Size*2);
BinToHex(ms.Memory^,Pansichar(t),ms.Size);
Result:=Tstringlist.create;
repeat
s:=copy(t,1,LineLen);
Result.Add(s);
delete(t,1,LineLen);
until t='';
finally
ms.free
end;
end;
procedure HexToImage(HexData:TstringList; var Image:Timage);
var ms:TmemoryStream; s:String;
begin
ms:=TmemoryStream.Create;
s:=HexData.Text;
try
ms.Size := Length(s) div 2;
HexToBin(PChar(s), ms.Memory^, ms.Size);
Image.Picture.LoadFromStream(ms);
finally
ms.free
end;
end;
I liked #remy-lebeau explanation, but once one knows that the PNG image is well preserved in the hex data, it was straightforward to save the .dfm file subset posted above into so2.dfm and use the following perl one-liner to convert it to binary.
perl -ne 'END{$PNG=index $b,"PNG"; die "PNG" if $PNG<1; print substr $b,$PNG-1; } $b.=pack "H*",$1 if ( /object img1:/i ... /}/ ) and (/Picture.Data = {/i ... /}/) and m/^\s*([0-9A-F]+)}?\s*$/ ' so2.dfm > img1.png
Related
Can somebody guide me to extend this procedure in a way so it removes all Non Printable characters or replaces with SPACE before it saves the stream to file ? String is read from Binary and could be maximum of 1 MB size.
My Procedure :
var
i : Word;
FileName : TFileName;
SizeofFiles,posi : Integer;
fs, sStream: TFileStream;
SplitFileName: String;
begin
ProgressBar1.Position := 0;
FileName:= lblFilePath.Caption;
SizeofFiles := StrToInt(edt2.Text) ;
posi := StrToInt(edt1.text) ;
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
fs.Position := Posi ;
begin
SplitFileName := ChangeFileExt(FileName, '.'+ FormatFloat('000', i));
sStream := TFileStream.Create(SplitFileName, fmCreate or fmShareExclusive);
try
if fs.Size - fs.Position < SizeofFiles then
SizeofFiles := fs.Size - fs.Position;
sStream.CopyFrom(fs, SizeofFiles);
ProgressBar1.Position := Round((fs.Position / fs.Size) * 100);
finally
sStream.Free;
end;
end;
finally
fs.Free;
end;
end;
You won't be able to use TStream.CopyFrom() anymore. You would have to Read(Buffer)() from the source TStream into a local byte array, strip off whatever you don't want from that array, and then Write(Buffer)() the remaining bytes to the destination TStream.
Here is a simple demo that should do what you want:
const
SrcFileName : String = 'Test.txt';
DstFileName : String = 'TestResult.txt';
StartPosition : Int64 = 50;
procedure TForm1.Button1Click(Sender: TObject);
var
FS : TFileStream;
Buf : TBytes;
I : Integer;
begin
// Read the source file from starting position
FS := TFileStream.Create(SrcFileName, fmOpenRead or fmShareDenyWrite);
try
FS.Position := StartPosition;
SetLength(Buf, FS.Size - FS.Position);
FS.Read(Buf[0], Length(Buf));
finally
FreeAndNil(FS);
end;
// Replace all non printable character by a space
// Assume file content is ASCII characters
for I := 0 to Length(Buf) - 1 do begin
// You may want to make a more complex test for printable of not
if (Ord(Buf[I]) < Ord(' ')) or (Ord(Buf[I]) > 126) then
Buf[I] := Ord(' ');
end;
// Write destination file
FS := TFileStream.Create(DstFileName, fmCreate);
try
FS.Write(Buf[0], Length(Buf));
finally
FreeAndNil(FS);
end;
end;
This code assume the file is pure ASCII text and that every character whose ASCII code is below 32 (space) or above 126 is not printable. This may not be the case for European languages. You'll easily adapt the test to fit your needs.
The source file could also be Unicode (16 bits characters). You should use a buffer made of Unicode characters or 16 bit integers (Word). And adapt the test for printable.
Could also be UTF8...
I've written a Delphi program which creates MJPEG files, which can be several GB in length. The JPGs are grabbed from a DirectX camera using DSPack. That part works fine and creates a file of JPG images in the format:
FF D8 ....(image data)... FF D9 FF D8 .... (image data)... FF D9 FF D8 etc
FF D8 marks the start of a JPG and FF D9 marks the end. Each JPG is around 21KB in size.
Now, I'm trying to write a matching MJPEG player.
In the Form's FormCreate procedure, I create a FileStream and display the first JPG which works fine:
procedure TForm1.FormCreate(Sender: TObject);
var
b: Array[0..1] of Byte;
jpg: TJPEGImage;
begin
:
:
MemoryStream:= TMemoryStream.Create;
jpg:= TJPEGImage.Create;
MJPEGStream:= TFileStream.Create(MJPEGFileName, fmOpenRead);
MJPEGStream.Position:= 0;
repeat
MJPEGStream.Read(b[0], 2); // Find end of first jpg
MemoryStream.Write(b[0], 2); // and write to memory
until (b[0] = $FF) and (b[1] = $D9);
MemoryStream.Position:= 0;
jpg.LoadFromStream(memoryStream);
Image1.Picture.Assign(jpg);
MemoryStream.Free;
jpg.Free;
end;
I leave the FileStream open so, hopefully, its Position pointer is retained.
I have a button on the form, the intention being to jog forwards one JPG at a time but, although the first 'jog' advances one JPG, subsequent jogs advance a random number of times. Here's the procedure:
procedure TForm1.btnJogForwardClick(Sender: TObject);
var
b: Array[0..1] of Byte;
jpg: TJPEGImage;
begin
MemoryStream:= TMemoryStream.Create;
try
repeat
MJPEGStream.Read(b[0], 2);
MemoryStream.Write(b[0], 2);
until ((b[0] = $FF) and (b[1] = $D9));
MemoryStream.Position:= 0;
jpg:= TJPEGImage.Create;
try
try
jpg.LoadFromStream(MemoryStream);
Image1.Picture.Assign(jpg);
except
end;
finally
jpg.Free;
end;
finally
MemoryStream.Free;
end;
I've checked with a 3rd Party MJPEG player and that is able to jog frame by frame so I know the MJPEG file is ok. Any suggestions as to why my procedure isn't stepping uniformly frame by frame would be appreciated.
Thanks,
John.
Thanks for the comments and suggestions. I think I've managed to sort it.
const
JPGSizeMax = 100000;
procedure TForm1.FormCreate(Sender: TObject);
var
b: Array[0..JPGSizeMax] of Byte;
:
:
begin
:
:
MJPEGStream:= TFileStream.Create(MJPEGFileName, fmOpenRead);
MJPEGStream.Position:= 0;
MJPEGStream.Read(b[0], JPGSizeMax);
for i:= 0 to JPGSizeMax do
begin
if (b[i] = $D9) and (b[i-1] = $FF) then
begin
Count:= i;
break;
end;
end;
MemoryStream.Write(b[0], Count);
FilePosition:= Count + 1;
MemoryStream.Position:= 0;
jpg.LoadFromStream(memoryStream);
Image1.Picture.Assign(jpg);
MemoryStream.Free;
jpg.Free;
end;
The procedure for the Jog button is much the same:
MJPEGStream.Position:= FilePosition;
MJPEGStream.Read(b[0], JPGSizeMax);
for i:= 0 to JPGSizeMax do
begin
if (b[i] = $D9) and (b[i-1] = $FF) then
begin
Count:= i;
break;
end;
end;
memoryStream.Write(b[0], Count);
FilePosition:= FilePosition + count + 1;
// etc
Thanks again for pointing me in the right direction.
John.
I need to create the following formats all together on the clipboard:
CF_BITMAP
CF_DIB
CF_DIB5
HTML Format
This is a console program which can create either the picture formats OR the HTML Format, but not all together on the clipboard:
program CopyImageFromFile;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
Vcl.Clipbrd,
Vcl.ExtCtrls,
Vcl.Imaging.pngimage,
System.SysUtils;
function FormatHTMLClipboardHeader(HTMLText: string): string;
const
CrLf = #13#10;
begin
Result := 'Version:0.9' + CrLf;
Result := Result + 'StartHTML:-1' + CrLf;
Result := Result + 'EndHTML:-1' + CrLf;
Result := Result + 'StartFragment:000081' + CrLf;
Result := Result + 'EndFragment:°°°°°°' + CrLf;
Result := Result + HTMLText + CrLf;
Result := StringReplace(Result, '°°°°°°', Format('%.6d', [Length(Result)]), []);
end;
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
gMem: HGLOBAL;
lp: PChar;
Strings: array[0..1] of AnsiString;
Formats: array[0..1] of UINT;
i: Integer;
ThisImage: TImage;
MyFormat: Word;
Bitmap: TBitMap;
AData: THandle;
APalette: HPALETTE;
begin
gMem := 0;
//{$IFNDEF USEVCLCLIPBOARD}
//Win32Check(OpenClipBoard(0));
//{$ENDIF}
Clipboard.Open;
try
//most descriptive first as per api docs
Strings[0] := FormatHTMLClipboardHeader(htmlStr);
Strings[1] := str;
Formats[0] := RegisterClipboardFormat('HTML Format');
Formats[1] := CF_TEXT;
{$IFNDEF USEVCLCLIPBOARD}
Win32Check(EmptyClipBoard);
{$ENDIF}
for i := 0 to High(Strings) do
begin
if Strings[i] = '' then Continue;
//an extra "1" for the null terminator
gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(Strings[i]) + 1);
{Succeeded, now read the stream contents into the memory the pointer points at}
try
Win32Check(gmem <> 0);
lp := GlobalLock(gMem);
Win32Check(lp <> nil);
CopyMemory(lp, PChar(Strings[i]), Length(Strings[i]) + 1);
finally
GlobalUnlock(gMem);
end;
Win32Check(gmem <> 0);
SetClipboardData(Formats[i], gMEm);
Win32Check(gmem <> 0);
gmem := 0;
end;
ThisImage := TImage.Create(nil);
try
ThisImage.Picture.LoadFromFile(APngFile);
// Comment this out to copy only the HTML Format:
Clipboard.Assign(ThisImage.Picture);
{MyFormat := CF_PICTURE;
ThisImage.Picture.SaveToClipBoardFormat(MyFormat, AData, APalette);
ClipBoard.SetAsHandle(MyFormat, AData);}
finally
ThisImage.Free;
end;
finally
//{$IFNDEF USEVCLCLIPBOARD}
//Win32Check(CloseClipBoard);
//{$ENDIF}
Clipboard.Close;
end;
end;
var
HTML: string;
begin
try
// Usage: CopyImageFromFile.exe test.png
// test.png is 32 bit with alpha channel
if ParamCount = 1 then
begin
if FileExists(ParamStr(1)) then
begin
if LowerCase(ExtractFileExt(ParamStr(1))) = '.png' then
begin
HTML := '<img border="0" src="file:///' + ParamStr(1) + '">';
CopyHTMLAndImageToClipBoard('test', ParamStr(1), HTML);
end;
end;
end;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end;
end;
end.
So how can I create all these formats together on the clipboard?
TClipboard empties the clipboard the first time you use a TClipboard method to put data on the clipboard (TClipboard.Assign(), TClipboard.SetBuffer(), TClipboard.SetAsHandle(), etc) after calling Open(). TClipboard expects you to use only its methods for accessing the clipboard, so your use of SetClpboardData() directly to store your string data is bypassing TClipboard's internal logic, thus your call to Assign() is seen as the first clipboard write and TClipboard wipes out any data you stored with SetClipboardData().
To avoid that, you have a few choices:
Assign() your image to the clipboard first, then save your string items with SetClipboardData() afterwards.
don't use Assign() at all. Use TPicture.SaveToClipboardFormat() directly and then call SetClipboardData().
don't use SetClipboardData() directly unless USEVCLCLIPBOARD is not defined. Use TClipboard.SetAsHandle() instead.
I would suggest #3. Let TClipboard do all of the work:
var
CF_HTML: UINT = 0;
// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
TClipboardAccess = class(TClipboard)
end;
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
TmpHtmlStr: AnsiString;
ThisImage: TPicture;
begin
Clipboard.Open;
try
//most descriptive first as per api docs
TmpHtmlStr := FormatHTMLClipboardHeader(htmlStr);
TClipboardAccess(Clipboard).SetBuffer(CF_HTML, PAnsiChar(TmpHtmlStr)^, Length(TmpHtmlStr) + 1);
TClipboardAccess(Clipboard).SetBuffer(CF_TEXT, PAnsiChar(Str)^, Length(Str) + 1);
ThisImage := TPicture.Create;
try
ThisImage.LoadFromFile(APngFile);
Clipboard.Assign(ThisImage);
finally
ThisImage.Free;
end;
finally
Clipboard.Close;
end;
end;
initialization
CF_HTML := RegisterClipboardFormat('HTML Format');
If you really need to support {$IFNDEF USEVCLCLIPBOARD} then you cannot use TClipboard at all, eg:
var
CF_HTML: UINT = 0;
{$IFDEF USEVCLCLIPBOARD}
// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
TClipboardAccess = class(TClipboard)
end;
{$ENDIF}
procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
ThisImage: TPicture;
{$IFNDEF USEVCLCLIPBOARD}
ImgData: THandle;
ImgFormat: Word;
ImgPalette: HPALETTE;
{$ENDIF}
procedure SetAsText(Format: UINT; const S: AnsiString);
{$IFNDEF USEVCLCLIPBOARD}
var
gMem: HGLOBAL;
lp: PAnsiChar;
{$ENDIF}
begin
{$IFDEF USEVCLCLIPBOARD}
TClipboardAccess(Clipboard).SetBuffer(Format, PAnsiChar(S)^, Length(S) + 1);
{$ELSE}
//an extra "1" for the null terminator
gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(S) + 1);
Win32Check(gmem <> 0);
try
{Succeeded, now read the stream contents into the memory the pointer points at}
lp := GlobalLock(gMem);
Win32Check(lp <> nil);
try
CopyMemory(lp, PAnsiChar(S), Length(S) + 1);
finally
GlobalUnlock(gMem);
end;
except
GlobalFree(gMem);
raise;
end;
SetClipboardData(Format, gMem);
{$ENDIF}
end;
begin
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Open;
{$ELSE}
Win32Check(OpenClipBoard(0));
{$ENDIF}
try
//most descriptive first as per api docs
SetAsText(CF_HTML, FormatHTMLClipboardHeader(htmlStr));
SetAsText(CF_TEXT, Str);
ThisImage := TPicture.Create;
try
ThisImage.LoadFromFile(APngFile);
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Assign(ThisImage);
{$ELSE}
ImgPalette := 0;
ThisImage.SaveToClipboardFormat(ImgFormat, ImgData, ImgPalette);
SetClipboardData(ImgFormat, ImgData);
if ImgPalette <> 0 then
SetClipboardData(CF_PALETTE, ImgPalette);
{$ENDIF}
finally
ThisImage.Free;
end;
finally
{$IFDEF USEVCLCLIPBOARD}
Clipboard.Close;
{$ELSE}
Win32Check(CloseClipBoard);
{$ENDIF}
end;
end;
initialization
CF_HTML := RegisterClipboardFormat('HTML Format');
David is right. You need to have one pair of open/close, and only one EmptyClipboard. You need to iterate through your formats and call SetClipboardData for each one.
RegisterClipboardFormat should only be called once, so do that in some initialization routine.
I would also try to avoid doing any file I/O once you've opened the clipboard, as you don't want to hold it open longer than necessary. i.e. read your pictures from disk first, if possible.
How can i save the pngimage to file copied form AdobeFirewoks(Clipboard) or Photoshop without losing the transparency.
i am using delphi2009.
thank you in advance.
#TLama
I tried this code but there is no transparency. I don't know also if i do it right.
png := TPngimage.Create;
try
png.LoadFromClipboardFormat(CF_BITMAP,
Clipboard.GetAsHandle(CF_BITMAP), CF_BITMAP);
image1.Picture.Assign(png);
finally
png.Free;
end;
Photoshop's clipboard format is horrible. The only pretty valid data that contains the alpha channel stored into the clipboard is... guess? ... a pointer to the alpha channel's memory into the "Photoshop Paste In Place" chunk.... HORRIBLE. If you copy something then restart photoshop, the alpha is... lost :)
However, you can easily understand if the clipboard contains Photoshop image.
Ask the Clipboard what chunks it have.
If the clipboard have two chunks, named "Photoshop Paste In Place" AND "Object Descriptor", you can be 99.9% sure that Photoshop IS RUNNING on the system AND Clipboard contains reference to Photoshop data. (When Photoshop quits, the Object Descriptor chunk gets removed from the Clipboard, so the alpha is lost forever)
So then, you have two choices:
Choice 1 (not recommended): Open Photoshop's Process Memory and read the raw 32-bit image data from the pointer... which is overall idiotic to do and unsecure, or
Choice 2 (recommended): Use COM to extract the image data from Photoshop. Of course, the COM method is the best way. Make your program generate and run the following VBS script:
On Error Resume Next
Set Ps = CreateObject("Photoshop.Application")
Set Shell = CreateObject("WScript.Shell")
Set FileSystem = CreateObject("Scripting.FileSystemObject")
Dim PNGFileName
PNGFileName = Shell.CurrentDirectory & "\psClipboard.png"
If FileSystem.FileExists(PNGFileName) Then
FileSystem.DeleteFile PNGFileName
End If
Set Doc = Ps.Documents.Add(1,1,72,"psClipboard",,3)
Doc.Paste()
Doc.RevealAll()
If Err.Number = 0 Then
set PNGSaveOptions = CreateObject("Photoshop.PNGSaveOptions")
doc.saveAs PNGFileName, PNGSaveOptions
End If
doc.Close()
In the script's CurrentDirectory, a file names "psClipboard.png" will be generated. Read this file in your program using libPng or whatever, and treat is as if it was come from the Clipboard. This script will DELETE the psClipboard.png, then will ask Photoshop for it. In case a Paste returns Error, the script will cease and the file will not be generated, in which case, Clipboard didn't contained valid Photoshop reference data.
Based on empirical results confirmed by my colleague having Adobe Photoshop CS 6 13.0 x32 using the following test code points out that it's not possible to save the image from clipboard copied by the Adobe Photoshop without losing transparency simply because it doesn't copy the alpha channel data.
Adobe Photoshop (at least in the version mentioned above) uses 24-bit pixel format for clipboard image data transfer. And, since it is the 24-bit bitmap there can't be an alpha channel. Don't know anyone who has the Adobe Fireworks to verify, but for sure they're using own registered clipboard format to transfer images including the alpha channel between their products.
The CF_BITMAP or CF_DIB formats used by Adobe Photoshop clipboard supposedly supports alpha channel, as some people says (I haven't tried) but that would be true only for 32-bit pixel format, not for the 24-bit pixel format. The only clipboard format, that surely supports transparency, is the CF_DIBV5 but as the others, the image have to be stored in 32-bit pixel format to preserve the alpha channel:
The following code shows the information about the currently copied clipboard content:
uses
ActiveX;
function GetClipboardFormatString(Format: Word): string;
var
S: string;
begin
case Format of
1: S := 'CF_TEXT';
2: S := 'CF_BITMAP';
3: S := 'CF_METAFILEPICT';
4: S := 'CF_SYLK';
5: S := 'CF_DIF';
6: S := 'CF_TIFF';
7: S := 'CF_OEMTEXT';
8: S := 'CF_DIB';
9: S := 'CF_PALETTE';
10: S := 'CF_PENDATA';
11: S := 'CF_RIFF';
12: S := 'CF_WAVE';
13: S := 'CF_UNICODETEXT';
14: S := 'CF_ENHMETAFILE';
15: S := 'CF_HDROP';
16: S := 'CF_LOCALE';
17: S := 'CF_DIBV5';
$0080: S := 'CF_OWNERDISPLAY';
$0081: S := 'CF_DSPTEXT';
$0082: S := 'CF_DSPBITMAP';
$0083: S := 'CF_DSPMETAFILEPICT';
$008E: S := 'CF_DSPENHMETAFILE';
$0200: S := 'CF_PRIVATEFIRST';
$02FF: S := 'CF_PRIVATELAST';
$0300: S := 'CF_GDIOBJFIRST';
$03FF: S := 'CF_GDIOBJLAST';
else
begin
SetLength(S, 255);
SetLength(S, GetClipboardFormatName(Format, PChar(S), 255));
if Length(S) = 0 then
S := 'Unknown, unregistered clipboard format';
Result := S + ' (' + IntToStr(Format) + ')';
Exit;
end;
end;
Result := 'Standard clipboard format (' + S + ')';
end;
function GetClipboardFormats: string;
var
S: string;
FormatEtc: TFormatEtc;
DataObject: IDataObject;
EnumFormatEtc: IEnumFormatEtc;
begin
Result := '';
if Succeeded(OleGetClipboard(DataObject)) then
begin
if Succeeded(DataObject.EnumFormatEtc(DATADIR_GET, EnumFormatEtc)) then
begin
S := DupeString('-', 65) + sLineBreak +
'Clipboard data formats: ' + sLineBreak +
DupeString('-', 65) + sLineBreak;
while EnumFormatEtc.Next(1, FormatEtc, nil) = S_OK do
S := S + GetClipboardFormatString(FormatEtc.cfFormat) + sLineBreak;
Result := S;
end;
end;
end;
function GetClipboardInfoDIB: string;
var
S: string;
ClipboardData: HGLOBAL;
BitmapInfoHeader: PBitmapInfoHeader;
const
BI_JPEG = 4;
BI_PNG = 5;
begin
Result := '';
if OpenClipboard(0) then
try
ClipboardData := GetClipboardData(CF_DIB);
if ClipboardData <> 0 then
begin
BitmapInfoHeader := GlobalLock(ClipboardData);
if Assigned(BitmapInfoHeader) then
try
S := DupeString('-', 65) + sLineBreak +
'Clipboard data of CF_DIB format: ' + sLineBreak +
DupeString('-', 65) + sLineBreak +
'Width: ' + IntToStr(BitmapInfoHeader.biWidth) + ' px' + sLineBreak +
'Height: ' + IntToStr(BitmapInfoHeader.biHeight) + ' px' + sLineBreak +
'Bit depth: ' + IntToStr(BitmapInfoHeader.biBitCount) + ' bpp' + sLineBreak +
'Compression format: ';
case BitmapInfoHeader.biCompression of
BI_RGB: S := S + 'Uncompressed format (BI_RGB)';
BI_RLE8: S := S + 'RLE format for bitmaps with 8 bpp (BI_RLE8)';
BI_RLE4: S := S + 'RLE format for bitmaps with 4 bpp (BI_RLE4)';
BI_BITFIELDS: S := S + 'Not compressed with color masks (BI_BITFIELDS)';
BI_JPEG: S := S + 'Compressed using JPEG file format (BI_JPEG)';
BI_PNG: S := S + 'Compressed using PNG file format (BI_PNG)';
end;
S := S + sLineBreak;
Result := S;
finally
GlobalUnlock(ClipboardData);
end;
end;
finally
CloseClipboard;
end;
end;
function GetClipboardInfoDIBV5: string;
var
S: string;
ClipboardData: HGLOBAL;
BitmapInfoHeader: PBitmapV5Header;
const
BI_JPEG = 4;
BI_PNG = 5;
begin
Result := '';
if OpenClipboard(0) then
try
ClipboardData := GetClipboardData(CF_DIBV5);
if ClipboardData <> 0 then
begin
BitmapInfoHeader := GlobalLock(ClipboardData);
if Assigned(BitmapInfoHeader) then
try
S := DupeString('-', 65) + sLineBreak +
'Clipboard data of CF_DIBV5 format: ' + sLineBreak +
DupeString('-', 65) + sLineBreak +
'Width: ' + IntToStr(BitmapInfoHeader.bV5Width) + ' px' + sLineBreak +
'Height: ' + IntToStr(BitmapInfoHeader.bV5Height) + ' px' + sLineBreak +
'Bit depth: ' + IntToStr(BitmapInfoHeader.bV5BitCount) + ' bpp' + sLineBreak +
'Compression format: ';
case BitmapInfoHeader.bV5Compression of
BI_RGB: S := S + 'Uncompressed format (BI_RGB)';
BI_RLE8: S := S + 'RLE format for bitmaps with 8 bpp (BI_RLE8)';
BI_RLE4: S := S + 'RLE format for bitmaps with 4 bpp (BI_RLE4)';
BI_BITFIELDS: S := S + 'Not compressed with color masks (BI_BITFIELDS)';
BI_JPEG: S := S + 'Compressed using JPEG file format (BI_JPEG)';
BI_PNG: S := S + 'Compressed using PNG file format (BI_PNG)';
end;
S := S + sLineBreak;
Result := S;
finally
GlobalUnlock(ClipboardData);
end;
end;
finally
CloseClipboard;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
begin
S := GetClipboardFormats;
if IsClipboardFormatAvailable(CF_DIB) then
S := S + sLineBreak + GetClipboardInfoDIB;
if IsClipboardFormatAvailable(CF_DIBV5) then
S := S + sLineBreak + GetClipboardInfoDIBV5;
ShowMessage(S);
end;
Output of the above code for transparent image copied into a clipboard by Adobe Photoshop CS 6 13.0 (click to enlarge):
Something useful to read:
How to copy an image to clipboard keeping its transparency
How to copy & paste images using CF_DIBV5 format preserving transparency
The solution explained in this link may work.
unit EG_ClipboardBitmap32;
{
Author William Egge. egge#eggcentric.com
January 17, 2002
Compiles with ver 1.2 patch #1 of Graphics32
This unit will copy and paste Bitmap32 pixels to the clipboard and retain the
alpha channel.
The clipboard data will still work with regular paint programs because this
unit adds a new format only for the alpha channel and is kept seperate from
the regular bitmap storage.
}
interface
uses
ClipBrd, Windows, SysUtils, GR32;
procedure CopyBitmap32ToClipboard(const Source: TBitmap32);
procedure PasteBitmap32FromClipboard(const Dest: TBitmap32);
function CanPasteBitmap32: Boolean;
implementation
const
RegisterName = 'G32 Bitmap32 Alpha Channel';
GlobalUnlockBugErrorCode = ERROR_INVALID_PARAMETER;
var
FAlphaFormatHandle: Word = 0;
procedure RaiseSysError;
var
ErrCode: LongWord;
begin
ErrCode := GetLastError();
if ErrCode <> NO_ERROR then
raise Exception.Create(SysErrorMessage(ErrCode));
end;
function GetAlphaFormatHandle: Word;
begin
if FAlphaFormatHandle = 0 then
begin
FAlphaFormatHandle := RegisterClipboardFormat(RegisterName);
if FAlphaFormatHandle = 0 then
RaiseSysError;
end;
Result := FAlphaFormatHandle;
end;
function CanPasteBitmap32: Boolean;
begin
Result := Clipboard.HasFormat(CF_BITMAP);
end;
procedure CopyBitmap32ToClipboard(const Source: TBitmap32);
var
H: HGLOBAL;
Bytes: LongWord;
P, Alpha: PByte;
I: Integer;
begin
Clipboard.Assign(Source);
if not OpenClipboard(0) then
RaiseSysError
else
try
Bytes := 4 + (Source.Width * Source.Height);
H := GlobalAlloc(GMEM_MOVEABLE and GMEM_DDESHARE, Bytes);
if H = 0 then
RaiseSysError;
P := GlobalLock(H);
if P = nil then
RaiseSysError
else
try
PLongWord(P)^ := Bytes - 4;
Inc(P, 4);
// Copy Alpha into Array
Alpha := Pointer(Source.Bits);
Inc(Alpha, 3); // Align with Alpha
for I := 1 to (Source.Width * Source.Height) do
begin
P^ := Alpha^;
Inc(Alpha, 4);
Inc(P);
end;
finally
if (not GlobalUnlock(H)) then
if (GetLastError() <> GlobalUnlockBugErrorCode) then
RaiseSysError;
end;
SetClipboardData(GetAlphaFormatHandle, H);
finally
if not CloseClipboard then
RaiseSysError;
end;
end;
procedure PasteBitmap32FromClipboard(const Dest: TBitmap32);
var
H: HGLOBAL;
ClipAlpha, Alpha: PByte;
I, Count, PixelCount: LongWord;
begin
if Clipboard.HasFormat(CF_BITMAP) then
begin
Dest.BeginUpdate;
try
Dest.Assign(Clipboard);
if not OpenClipboard(0) then
RaiseSysError
else
try
H := GetClipboardData(GetAlphaFormatHandle);
if H <> 0 then
begin
ClipAlpha := GlobalLock(H);
if ClipAlpha = nil then
RaiseSysError
else
try
Alpha := Pointer(Dest.Bits);
Inc(Alpha, 3); // Align with Alpha
Count := PLongWord(ClipAlpha)^;
Inc(ClipAlpha, 4);
PixelCount := Dest.Width * Dest.Height;
Assert(Count = PixelCount,
'Alpha Count does not match Bitmap pixel Count,
PasteBitmap32FromClipboard(const Dest: TBitmap32);');
// Should not happen, but if it does then this is a safety catch.
if Count > PixelCount then
Count := PixelCount;
for I := 1 to Count do
begin
Alpha^ := ClipAlpha^;
Inc(Alpha, 4);
Inc(ClipAlpha);
end;
finally
if (not GlobalUnlock(H)) then
if (GetLastError() <> GlobalUnlockBugErrorCode) then
RaiseSysError;
end;
end;
finally
if not CloseClipboard then
RaiseSysError;
end;
finally
Dest.EndUpdate;
Dest.Changed;
end;
end;
end;
end.
The function PasteBitmap32FromClipboard is apparently what you need. Saving a bitmap as PNG is answered in this question.
I am trying to make a basic Hex viewer out of a TMemo, I know this is probably not ideal but it will be only me personally using it so that does not really matter.
(1)
Firstly, suppose a Memo is filled with Hex information like so:
How could I get a count of all the text blocks shown, ignoring the white space? So using the image the result in this case would be 28.
This is what I tried and I know it is completely wrong as I am accessing the Memo lines but I don't know how to access each character.
I cant seem to solve this simple problem :(
function CountWordBlocks(Memo: TMemo): Integer;
var
i: Integer;
vCount: Integer;
begin
for i := 0 to Memo.Lines.Count - 1 do
begin
if Length(Memo.Lines.Strings[i]) = 2 then
begin
Inc(vCount);
end;
end;
Result := vCount;
end;
Here is the code I am using to display the Hex values in the Memo:
procedure ReadFileAsHex(const AFileName: string; ADestination: TStrings);
var
fs: TFileStream;
buff: Byte;
linecount: Byte;
line: string;
begin
linecount := 0;
line := '';
fs := TFileStream.Create(AFileName, fmOpenRead);
try
ADestination.BeginUpdate;
try
while fs.Position < fs.Size do
begin
fs.Read(buff, 1);
line := line + IntToHex(buff, 2) + ' ';
Inc(linecount);
if linecount = 16 then
begin
ADestination.Add(line);
line := '';
linecount := 0;
end;
end;
if Length(line) <> 0 then
ADestination.Add(line);
finally
ADestination.EndUpdate;
end;
finally
fs.Free;
end;
end;
(2)
If I click onto the Memo and a text block is under the cursor, how could I know which number the selected block is out of all the others?
So using the same first image, the caret is at the top line next to 68, so the result would be 3 as it is the third text block out of 28.
This should be so easy but I cannot think clearly, I don't have the right programming mind yet and so really struggle with basic logic and solving problems!
(3)
Finally I would like to select a block at runtime by passing a block number value. I tried this without much success:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
vRead: Integer;
begin
txt := Memo.Text;
vRead:= 0;
PrevWhite := True;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
Inc(vRead);
PrevWhite := False;
end;
PrevWhite := ThisWhite;
if vRead = BlockNumber then
begin
Memo.SelStart := vRead;
Memo.SetFocus;
Exit;
end;
end;
end;
(1)
This works:
function TForm1.CountBlocks: integer;
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
begin
txt := Memo1.Text;
result:= 0;
PrevWhite := true;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
inc(result);
PrevWhite := false;
end;
PrevWhite := ThisWhite;
end;
end;
However, it can be optimized if more detailed information about the memo contents is available. For instance, if you know that each line consists of four blocks, then the number of blocks is simply 4*Memo1.Lines.Count. My code above will even accept blocks of different width.
(2)
Simply replace
for i := 1 to Length(txt) do
by
for i := 1 to Memo1.SelStart + 1 do
Since you are in control of the formatting of your lines, and the lines have a fixed format, it is very easy to calculate the number of bytes being displayed without resorting to looping through the individual lines one a time. Every line displays 3 characters per byte, and every line other than the last line displays 16 bytes, thus 48 characters per complete 16-byte line. Use those facts to your advantage to calculate the number of bytes based on the number of complete 16-byte lines present, and then you can add on the number of remaining bytes from just the last line:
function CountWordBlocks(Memo: TMemo): Integer;
var
Count: Integer;
begin
Count := Memo.Lines.Count;
if Count > 0 then
Result := (16 * (Count-1)) + (Length(Memo.Lines[Count-1]) div 3);
else
Result := 0;
end;
You can do something similar to translate a character offset within the Memo into a work block number:
function GetCurrentWordBlock(Memo: TMemo): Integer;
var
SelStart, LineStart, LineNum: Integer
begin
Result := 0;
SelStart := Memo.SelStart;
if SelStart < 0 then Exit;
LineStart := Memo.Perform(EM_LINEINDEX, SelStart, 0);
if LineStart < 0 then Exit;
LineNum := Memo.Perform(EM_LINEFROMCHAR, LineStart, 0);
Result := (16 * LineNum) + ((SelStart - LineStart) div 3) + 1;
end;
To select a given block number, you can do this:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
LineNum, LineStart: Integer;
begin
if BlockNumber < 1 then Exit;
LineNum = (BlockNumber - 1) div 16;
LineStart = Memo.Perform(EM_LINEINDEX, LineNum, 0);
if LineStart < 0 then Exit;
Memo.SelStart = LineStart + (((BlockNumber - 1) - (16 * LineNum)) * 3);
Memo.SelLength := 2;
Memo.SetFocus;
end;