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.
Related
How can I add a large text file (actually a HTML file) as an attachment to a channel in Slack? A working example would be great. I use SDriver - the included demo works fine to send some strings, but I don't find anything on how to use attachments.
What I did so far:
procedure TForm1.SendActionExecute(Sender: TObject);
var
LWebHook: IMessageBuffer;
LMessage: IMessage;
LStopWatch: TStopWatch;
vAttachment: IAttachment;
vField: IFields;
begin
LStopWatch := TStopWatch.StartNew;
LMessage := TMessage.Create(EditMessage.Text + ' [' + TimeToStr(Now) + ']');
LMessage.UserName := EditUserName.Text;
LMessage.Icon_URL := EditIcon_URL.Text;
LMessage.Icon_Emoji := EditIcon_Emoji.Text;
LMessage.Channel := EditChannel.Text;
vAttachment := LMessage.AddAttachment;
vField := vAttachment.AddFields;
vField.Title := 'Title';
vField.Value := 'Value';
///////////////////////////////////////////////////////
// How can I add a large text file as attachment here ?
///////////////////////////////////////////////////////
LWebHook := TIncomingWebHook.Create(EditWebHookURL.Text, False);
LWebHook.Push(LMessage);
LWebHook.Flush;
end;
i want to send emoji with indy 9.00.10 on delphi 7. i use tnt VCL Controls .
i found this url http://apps.timwhitlock.info/emoji/tables/unicode for unicode and bytes code.
how to convert this codes to delphi Constants for Send with indy.
i use this delphi code for send message to telegram bot:
procedure TBotThread.SendMessage(ChatID:String; Text : WideString;
parse_mode:string;disable_notification:boolean);
Var
Stream: TStringStream;
Params: TIdMultipartFormDataStream;
//Text : WideString;
msg : WideString;
Src : string;
LHandler: TIdSSLIOHandlerSocket;
begin
try
try
if FShowBotLink then
Text := Text + LineBreak + FBotUser;
msg := '/sendmessage';
Stream := TStringStream.Create('');
Params := TIdMultipartFormDataStream.Create;
Params.AddFormField('chat_id',ChatID);
if parse_mode <> '' then
Params.AddFormField('parse_mode',parse_mode);
if disable_notification then
Params.AddFormField('disable_notification','true')
else
Params.AddFormField('disable_notification','false');
Params.AddFormField('disable_web_page_preview','true');
Params.AddFormField('text',UTF8Encode(Text));
LHandler := TIdSSLIOHandlerSocket.Create(nil);
FidHttpSend.ReadTimeout := 30000;
FidHttpSend.IOHandler:=LHandler;
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmUnassigned;
FidHttpSend.HandleRedirects := true;
FidHttpSend.Post(BaseUrl + API + msg, Params, Stream);
finally
Params.Free;
Stream.Free;
ENd;
except
on E: EIdHTTPProtocolException do
begin
if E.ReplyErrorCode = 403 then
begin
WriteToLog('Bot was blocked by the user');
end;
end;
end;
end;
bytes sample for emojies:
AERIAL_TRAMWAY = '\xf0\x9f\x9a\xa1';
AIRPLANE = '\xe2\x9c\x88';
ALARM_CLOCK = '\xe2\x8f\xb0';
ALIEN_MONSTER = '\xf0\x9f\x91\xbe';
sorry for bad english!!!
The Telegram Bot API supports several forms of input:
We support GET and POST HTTP methods. We support four ways of passing parameters in Bot API requests:
URL query string
application/x-www-form-urlencoded
application/json (except for uploading files)
multipart/form-data (use to upload files)
You are using the last option.
Indy 9 does not support Delphi 2009+ or Unicode. All uses of string are assumed to be AnsiString, which is the case in Delphi 7. Any AnsiString you add to TIdMultipartFormDataStream or TStrings, even a UTF-8 encoded one, will be transmitted as-is by TIdHTTP. However, there is no option to specify to the server that the string data is actually using UTF-8 as a charset. But, according to the docs:
All queries must be made using UTF-8.
So not specifying an explicit charset might not be problem.
If you still have problems with multipart/form-data, then consider using application/x-www-form-urlencoded (use TIdHTTP.Post(TStrings)) or application/json (use TIdHTTP.Post(TStream)) instead:
procedure TBotThread.SendMessage(ChatID: String; Text: WideString; parse_mode: string; disable_notification: boolean);
var
Params: TStringList;
LHandler: TIdSSLIOHandlerSocket;
begin
if FShowBotLink then
Text := Text + LineBreak + FBotUser;
Params := TStringList.Create;
try
Params.Add('chat_id=' + UTF8Encode(ChatID));
if parse_mode <> '' then
Params.Add('parse_mode=' + UTF8Encode(parse_mode));
if disable_notification then
Params.Add('disable_notification=true')
else
Params.Add('disable_notification=false');
Params.Add('disable_web_page_preview=true');
Params.Add('text=' + UTF8Encode(Text));
LHandler := TIdSSLIOHandlerSocket.Create(nil);
try
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmClient;
FidHttpSend.HandleRedirects := true;
FidHttpSend.ReadTimeout := 30000;
FidHttpSend.IOHandler := LHandler;
try
try
FidHttpSend.Post(BaseUrl + API + '/sendmessage', Params, TStream(nil));
except
on E: EIdHTTPProtocolException do
begin
if E.ReplyErrorCode = 403 then
begin
WriteToLog('Bot was blocked by the user');
end;
end;
end;
finally
FidHttpSend.IOHandler := nil;
end;
finally
LHandler.Free;
end;
finally
Params.Free;
end;
end;
procedure TBotThread.SendMessage(ChatID: String; Text: WideString; parse_mode: string; disable_notification: boolean);
var
Params: TStringStream;
LHandler: TIdSSLIOHandlerSocket;
function JsonEncode(const wStr: WideString): string;
var
I: Integer;
Ch: WideChar;
begin
// JSON uses UTF-16 text, so no need to encode to UTF-8...
Result := '';
for I := 1 to Length(wStr) do
begin
Ch := wStr[i];
case Ch of
#8: Result := Result + '\b';
#9: Result := Result + '\t';
#10: Result := Result + '\n';
#12: Result := Result + '\f';
#13: Result := Result + '\r';
'"': Result := Result + '\"';
'\': Result := Result + '\\';
'/': Result := Result + '\/';
else
if (Ord(Ch) >= 32) and (Ord(Ch) <= 126) then
Result := Result + AnsiChar(Ord(wStr[i]))
else
Result := Result + '\u' + IntToHex(Ord(wStr[i]), 4);
end;
end;
end;
begin
if FShowBotLink then
Text := Text + LineBreak + FBotUser;
Params := TStringStream.Create('');
try
Params.WriteString('{');
Params.WriteString('chat_id: "' + JsonEncode(ChatID) + '",');
if parse_mode <> '' then
Params.WriteString('parse_mode: "' + JsonEncode(parse_mode) + '",')
if disable_notification then
Params.WriteString('disable_notification: True,')
else
Params.WriteString('disable_notification: False,');
Params.WriteString('disable_web_page_preview: True,');
Params.WriteString('text: "' + JsonEncode(Text) + '"');
Params.WriteString('}');
Params.Position := 0;
LHandler := TIdSSLIOHandlerSocket.Create(nil);
try
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmClient;
FidHttpSend.HandleRedirects := true;
FidHttpSend.ReadTimeout := 30000;
FidHttpSend.IOHandler := LHandler;
try
try
FidHttpSend.Request.ContentType := 'application/json';
FidHttpSend.Post(BaseUrl + API + '/sendmessage', Params, TStream(nil));
except
on E: EIdHTTPProtocolException do
begin
if E.ReplyErrorCode = 403 then
begin
WriteToLog('Bot was blocked by the user');
end;
end;
end;
finally
FidHttpSend.IOHandler := nil;
end;
finally
LHandler.Free;
end;
finally
Params.Free;
end;
end;
That being said, your function's Text parameter is a WideString, which uses UTF-16, so you should be able to send any Unicode text, including emojis. If you are trying to generate text in your code, just make sure you UTF-16 encode any non-ASCII characters correctly. For example, codepoint U+1F601 GRINNING FACE WITH SMILING EYES is wide chars $D83D $DE01 in UTF-16:
var
Text: WideString;
Text := 'hi ' + #$D83D#$DE01; // 'hi 😁'
SendMessage('#channel', Text, 'Markup', False);
Alternatively, you can use HTML in your text messages, so you can encode non-ASCII characters using numerical HTML entities. According to the docs:
All numerical HTML entities are supported.
Codepoint U+1F601 is numeric entity $#128513; in HTML:
var
Text: WideString;
Text := 'hi $#128513;'; // 'hi 😁'
SendMessage('#channel', Text, 'HTML', False);
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
I am loading a binary file into a memorystream, then encoding the data, and returning the result as a string, then writing the result into another memorystream, and saving it to a file, but when it saved the file is much smaller than the original 25kb from 400kb...lol, im pretty sure it's because I've hit the limit of what a string is capable of handling.
it's definately encoding what data it does save in the new file correctly, I decrypted it and compared it to the begining of the original file.
I know this is a very long winded method and probibly has some unnecesary steps, so loading it into bStream would be a very effective resolution. My question is how could I have the data returned to bStream rather than having it returned to a string then writing the string to bStream at that point as I do believe, that it would solve my problem, any other suggestions would also be appreciated. Im using Delphi 6.
Heres My Code:
function B64Encode(pInput: pointer; pOutput: pointer; Size: longint): longint;
var
i, iptr, optr: integer;
Input, Output: PByteArray;
begin
Input := PByteArray(pInput);
Output := PByteArray(pOutput);
iptr := 0;
optr := 0;
for i := 1 to (Size div 3) do
begin
Output^[optr + 0] := B64[Input^[iptr] shr 2];
Output^[optr + 1] := B64[((Input^[iptr] and 3) shl 4) + (Input^[iptr + 1] shr 4)];
Output^[optr + 2] := B64[((Input^[iptr + 1] and 15) shl 2) + (Input^[iptr + 2] shr 6)];
Output^[optr + 3] := B64[Input^[iptr + 2] and 63];
Inc(optr, 4);
Inc(iptr, 3);
end;
case (Size mod 3) of
1:
begin
Output^[optr + 0] := B64[Input^[iptr] shr 2];
Output^[optr + 1] := B64[(Input^[iptr] and 3) shl 4];
Output^[optr + 2] := byte('=');
Output^[optr + 3] := byte('=');
end;
2:
begin
Output^[optr + 0] := B64[Input^[iptr] shr 2];
Output^[optr + 1] := B64[((Input^[iptr] and 3) shl 4) + (Input^[iptr + 1] shr 4)];
Output^[optr + 2] := B64[(Input^[iptr + 1] and 15) shl 2];
Output^[optr + 3] := byte('=');
end;
end;
Result := ((Size + 2) div 3) * 4;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
aStream, bStream: TMemoryStream;
strastream: string;
szaStream: integer;
begin
bStream := TMemoryStream.Create;
aStream := TMemoryStream.Create;
aStream.LoadFromFile('C:\file1.exe');
szaStream := (astream.size + 2) div (3 * 4);
SetLength(strastream, szaStream);
B64Encode(astream.Memory, #strastream[1], Length(strastream));
bstream.WriteBuffer(strastream[1], szaStream);
AttachToFile('C:\file2.exe', bStream);
bstream.Free;
aStream.Free;
end;
Thanks.
Your length calculations are all wrong as has been pointed out in comments.
szaStream := (astream.size + 2) div (3 * 4);
This means that your encoded stream is 1/12th the size of the input stream. But it needs to be larger. You meant:
szaStream := ((astream.size * 4) div 3) + 2;
I also do not see the point of using a string here. You can write directly to the stream.
And, it's worth repeating that with base 64 you are encoding and not encrypting.
In my opinion, there is little point writing all this yourself when Delphi ships with a base 64 implementation. The unit is called EncdDecd, or Soap.EncdDecd if you are using namespaces. And the only function you need is
procedure EncodeStream(Input, Output: TStream);
Create two file streams, one for reading, one for writing, and pass them to that function. For example:
procedure EncodeFileBase64(const InFileName, OutFileName:string);
var
Input, Output: TStream;
begin
Input := TFileStream.Create(InFileName, fmOpenRead);
try
Output := TFileStream.Create(InFileName, fmCreate);
try
EncodeStream(Input, Output);
finally
Output.Free;
end;
finally
Input.Free;
end;
end;
Should you need to reverse the process, do so with, you guessed it, DecodeStream.
If performance matters then you may need to use a buffered stream rather than TFileStream. For example: Buffered files (for faster disk access)
I have searched the web for hours but I can not find anything about how to get the palette from a TPicture.Graphic. I also need to get the color values so I can pass these values to a TStringList for filling cells in a colorpicker.
Here is the code that I currently have:
procedure TFormMain.OpenImage1Click( Sender: TObject );
var
i: integer;
S: TStringList;
AColor: TColor;
AColorCount: integer;
N: string;
Pal: PLogPalette;
HPal: hPalette;
begin
if OpenPictureDialog1.Execute then
begin
Screen.Cursor := crHourGlass;
try
Pal := nil;
try
S := TStringList.Create;
ABitmap.Free; // Release any existing bitmap
ABitmap := TBitmap.Create;
Image1.Picture.LoadFromFile( OpenPictureDialog1.Filename );
ABitmap.Canvas.Draw( 0, 0, Image1.Picture.Graphic );
GetMem( Pal, Sizeof( TLogPalette ) + Sizeof( TPaletteEntry ) * 255 );
Pal.palversion := $300;
Pal.palnumentries := 256;
for i := 0 to 255 do
begin
AColor := Pal.PalPalEntry[ i ].PeRed shl 16 + Pal.PalPalEntry[ i ].PeGreen shl 8 + Pal.PalPalEntry[ i ].PeBlue;
N := ColorToString( AColor );
S.Add( N );
end;
HPal := CreatePalette( Pal^ );
ABitmap.Palette := HPal;
Memo1.Lines := S;
finally; FreeMem( Pal ); end;
S.Free;
finally; Screen.Cursor := crDefault; end;
end;
end;
I am drawing to the canvas of ABitmap with the image contained in Image1.Picture.Graphic because I want to support all TPicture image types such as Bitmap, Jpeg, PngImage, and GIfImg.
Any assistance would be appreciated. Am I on the correct path or is something different needed?
The code you posted does nothing really. You either have to read the palette back from the bitmap before you can access it, or you need to create a palette and assign it to a bitmap - your code does neither.
The following code is more or less yours, with fields fBitmap and fBitmapPalEntries for the results of the operation. I commented all the lines that I changed:
if OpenPictureDialog1.Execute then
begin
Screen.Cursor := crHourGlass;
try
Pal := nil;
try
S := TStringList.Create;
fBitmap.Free; // Release any existing bitmap
fBitmap := TBitmap.Create;
// if you want a 256 colour bitmap with a palette you need to say so
fBitmap.PixelFormat := pf8bit;
Image1.Picture.LoadFromFile( OpenPictureDialog1.Filename );
fBitmap.Canvas.Draw( 0, 0, Image1.Picture.Graphic );
// access the palette only if bitmap has indeed one
if fBitmap.Palette <> 0 then begin
GetMem( Pal, Sizeof( TLogPalette ) + Sizeof( TPaletteEntry ) * 255 );
Pal.palversion := $300;
Pal.palnumentries := 256;
// read palette data from bitmap
fBitmapPalEntries := GetPaletteEntries(fBitmap.Palette, 0, 256,
Pal.palPalEntry[0]);
for i := 0 to fBitmapPalEntries - 1 do
begin
AColor := Pal.PalPalEntry[ i ].PeRed shl 16
+ Pal.PalPalEntry[ i ].PeGreen shl 8
+ Pal.PalPalEntry[ i ].PeBlue;
N := ColorToString( AColor );
S.Add( N );
end;
// doesn't make sense, the palette is already there
// HPal := CreatePalette( Pal^ );
// fBitmap.Palette := HPal;
Memo1.Lines := S;
end;
finally; FreeMem( Pal ); end;
S.Free;
finally; Screen.Cursor := crDefault; end;
end;
Support for palettes with less entries is easy, you just need to reallocate the memory after you know how many entries there are, something like
ReallocMem(Pal, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * (fBitmapPalEntries - 1));
Creating a palette would only be necessary if you want to write a bitmap in pf4Bit or pf8Bit format. You would need to determine the 16 or 256 colours that are palette entries, possibly by reducing the number of colours (dithering). Then you would fill the palette colour slots with the colour values, and finally use the two lines I commented out from your code. You have to make sure that the pixel format of the bitmap and the number of palette entries match.
A wonderful resource of graphics alogithms is available at efg's reference library which includes a specific section dealing with just color. Specifically this article (with source) discusses counting the available colors and might be of the best use.
I don't know myself, but you might take a look at XN Resource Editor, which does display palette information, is written in Delphi and has source available.
Thank-you all.... especially mghie. We managed to get the code to work very well for bmp, png and gif files and pf1bit, pf4bit, pf8bit, pf16bit and pf24bit images. We are still tesing the code but so far it seems to work very well. Hopefully this code will help other developers as well.
var
i: integer;
fStringList: TStringList;
fColor: TColor;
fColorString: string;
fPal: PLogPalette;
fBitmapPalEntries: Cardinal;
begin
if OpenPictureDialog1.Execute then
begin
Screen.Cursor := crHourGlass;
try
fPal := nil;
try
fStringList := TStringList.Create;
Image1.Picture.LoadFromFile( OpenPictureDialog1.Filename );
if Image1.Picture.Graphic.Palette <> 0 then
begin
GetMem( fPal, Sizeof( TLogPalette ) + Sizeof( TPaletteEntry ) * 255 );
fPal.palversion := $300;
fPal.palnumentries := 256;
fBitmapPalEntries := GetPaletteEntries( Image1.Picture.Graphic.Palette, 0, 256, fPal.palPalEntry[ 0 ] );
for i := 0 to fBitmapPalEntries - 1 do
begin
fColor := fPal.PalPalEntry[ i ].PeBlue shl 16
+ fPal.PalPalEntry[ i ].PeGreen shl 8
+ fPal.PalPalEntry[ i ].PeRed;
fColorString := ColorToString( fColor );
fStringList.Add( fColorString );
end;
end;
finally; FreeMem( fPal ); end;
if fStringList.Count = 0 then
ShowMessage('No palette entries!')
else
// add the colors to the colorpicker here
fStringList.Free;
finally; Screen.Cursor := crDefault; end;
end;