KimHJ 3 Posted November 21 I have this code where I send an jpg image with TidTCPClient to a TidTCPServer. On the Server side the image aperea in a TImage, but no file is created when I save it. I tried using .jpg as well I try to use the MemoryStream.SaveToFile. I look in many forums and it looks like the do the same thing, what is wrong with this code? Here is the server code. procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var JSON: TJSONObject; StringStream: TStringStream; MemoryStream: TMemoryStream; begin MemoryStream := nil; StringStream := nil; JSON := nil; try StringStream := TStringStream.Create('', TEncoding.ASCII); AContext.Connection.IOHandler.LargeStream := True; AContext.Connection.IOHandler.ReadStream(StringStream, SizeOf(StringStream), True); JSON := TJSONObject.ParseJSONValue(StringStream.DataString) as TJSONObject; MemoryStream := TMemoryStream.Create; IdDecoderMIME1.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); TThread.Synchronize(nil, procedure begin Edit2.Text := ''; Edit2.Text := JSON.GetValue('message').Value; Image1.Bitmap.LoadFromStream(MemoryStream); end); finally MemoryStream.Free; StringStream.Free; JSON.Free; end; SaveImageToDisk; end; procedure TMainForm.SaveImageToDisk; var CsFilename: String; begin CsFilename := Edit2.Text + '.png'; Image1.Bitmap.SaveToFile(CsFilename); end; Share this post Link to post
Remy Lebeau 1435 Posted November 21 (edited) SizeOf(StringStream) This is wrong. This is the size of a pointer. How many bytes are you expecting the client to send? What does the client code look like? Ideally, the client should send the JSON's size before sending the JSON's data, and then the server should read the size before the data. IOHandler.Write(TStream) and IOHandler.ReadStream() can handle that for you, but not the way you are currently using IOHandler.ReadStream(). The client can call IOHandler.Write(TStream) with AWriteByteCount=True, and the server can call IOHandler.ReadStream() with AByteCount=-1 and AReadUntilDisconnect=False. That being said, since JSON is textual data, I would suggest using IOHandler.ReadString() instead of IOHandler.ReadStream(). Also, the preferred encoding for JSON is UTF-8 not ASCII. IdDecoderMIME1.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); ... Image1.Bitmap.LoadFromStream(MemoryStream); You are not rewinding the TMemoryStream back to position 0 before loading it. So the TImage ends up blank before you save it to file. Edited November 21 by Remy Lebeau Share this post Link to post
KimHJ 3 Posted November 21 Remy, I do see the image in the TImage on the server application after it is send from the client app. I by accident remove the rest to 0, but still no file are saved. Here is the Client side running on Android. procedure TMainForm.SendPicture; var MemoryStream: TMemoryStream; StringStream: TStringStream; JObj: TJSONObject; begin {$IFDEF MSWINDOWS} FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text); {$ENDIF} {$IFDEF ANDROID} FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, SetupForm.EditImage.Text); {$ENDIF} if not FileExists(FImageFilePath) then begin ShowMessage('No such file at ' + FImageFilePath + '!'); Exit; end; IdTCPClient1.Host := SetupForm.EditHost.Text; try IdTCPClient1.Port := Integer.Parse(SetupForm.EditPort.Text); except on EConvertError do begin ShowMessage('Wrong port value!'); Exit; end; end; if not IdTCPClient1.Connected then begin try IdTCPClient1.Connect; except on EIdSocketError do begin ShowMessage('Connection error!'); Exit; end; end; end else begin IdTCPClient1.Disconnect; Exit; end; TTask.Run( procedure begin MemoryStream := nil; StringStream := nil; JObj := nil; try MemoryStream := TMemoryStream.Create; MemoryStream.LoadFromFile(FImageFilePath); StringStream := TStringStream.Create(IdEncoderMIME1.Encode(MemoryStream),TEncoding.ASCII); JOBJ := TJSONObject.Create; JOBJ.AddPair('image_encoded', StringStream.DataString); JOBJ.AddPair('message', Edit1.Text); IdTCPClient1.Socket.WriteLn(JObj.ToJSON); TThread.Synchronize(nil, procedure begin ShowMessage('BASE64 Image and message successfully sent to server!'); end); finally IdTCPClient1.Disconnect; JObj.Free; StringStream.Free; MemoryStream.Free; end; end); end; Right no I'm testing the send and save on the server side, but eventually I would like to send and save an image displayed in a TImage on the client side and not a file. The TImage is from the camera. Share this post Link to post
Remy Lebeau 1435 Posted November 21 (edited) 9 hours ago, KimHJ said: I do see the image in the TImage on the server application after it is send from the client app. I don't see how that is possible given the bugs I mentioned in the code you showed. Also, I just now noticed another bug I didn't see earlier - your server code is not synchronizing the call to SaveImageToDisk(). 9 hours ago, KimHJ said: I by accident remove the rest to 0, but still no file are saved. OK, but the rest of the server code you showed is still buggy. Quote Here is the Client side running on Android. First off, you should not be calling TIdTCPClient.Connect() in the main UI thread. In fact, modern Android versions do not allow any network operations on the main thread at all. Second, that client send code does not match the server read code you showed earlier! Your client is sending the JSON using IOHandler.WriteLn(), but your server is reading the JSON using IOHandler.ReadStream(). That will not work! You need to use IOHandler.ReadLn() on the server side, eg: procedure TMainForm.SendPicture; var Host, Msg: string; Port: Integer; begin {$IFDEF MSWINDOWS} FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text); {$ENDIF} {$IFDEF ANDROID} FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, SetupForm.EditImage.Text); {$ENDIF} if not FileExists(FImageFilePath) then begin ShowMessage('No such file at ' + FImageFilePath + '!'); Exit; end; Host := SetupForm.EditHost.Text.Trim; if Host.IsEmpty then begin ShowMessage('Wrong host value!'); Exit; end; if not TryStrToInt(SetupForm.EditPort.Text, Port) then begin ShowMessage('Wrong port value!'); Exit; end; Msg := Edit1.Text; TTask.Run( procedure var MemoryStream: TMemoryStream; JObj: TJSONObject; JsonStr: string; begin try JObj := TJSONObject.Create; try MemoryStream := TMemoryStream.Create; try MemoryStream.LoadFromFile(FImageFilePath); JObj.AddPair('image_encoded', TIdEncoderMIME.EncodeStream(MemoryStream)); finally MemoryStream.Free; end; JObj.AddPair('message', Msg); JsonStr := JObj.ToJSON; finally JObj.Free; end; if IdTCPClient1.Connected then IdTCPClient1.Disconnect; IdTCPClient1.Host := Host; IdTCPClient1.Port := Port; try IdTCPClient1.Connect; except TThread.Queue(nil, procedure begin ShowMessage('Connection error!'); end); Exit; end; try try IdTCPClient1.IOHandler.WriteLn(JSonStr); finally IdTCPClient1.Disconnect; end; except TThread.Queue(nil, procedure begin ShowMessage('Send error!'); end); Exit; end; TThread.Queue(nil, procedure begin ShowMessage('BASE64 Image and message successfully sent to server!'); end); except on E: Exception do begin TThread.Queue(nil, procedure begin ShowMessage('Error! ' + E.Message); end); end; end; end); end; procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var JSON: TJSONObject; MemoryStream: TMemoryStream; JsonStr, Msg: String; begin AContext.Connection.IOHandler.MaxLineLength := MaxInt; JsonStr := AContext.Connection.IOHandler.ReadLn; MemoryStream := nil; try JSON := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject; try MemoryStream := TMemoryStream.Create; TIdDecoderMIME.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); Msg := JSON.GetValue('message').Value; finally JSON.Free; end; MemoryStream.Position := 0; TThread.Synchronize(nil, procedure begin Edit2.Text := Msg; Image1.Bitmap.LoadFromStream(MemoryStream); end); finally MemoryStream.Free; end; TThread.Synchronize(nil, SaveImageToDisk); end; However, if there is any possibility that the JSON may have any embedded line breaks, then using IOHandler.WriteLn/ReadLn will not work, so you should use IOHandler.Write(TStream) with IOHandler.ReadStream/ReadString() instead, eg: procedure TMainForm.SendPicture; var Host, Msg: string; Port: Integer; begin {$IFDEF MSWINDOWS} FImageFilePath := TPath.Combine(TPath.GetDocumentsPath, EditImage.Text); {$ENDIF} {$IFDEF ANDROID} FImageFilePath := TPath.Combine(TPath.GetSharedDocumentsPath, SetupForm.EditImage.Text); {$ENDIF} if not FileExists(FImageFilePath) then begin ShowMessage('No such file at ' + FImageFilePath + '!'); Exit; end; Host := SetupForm.EditHost.Text.Trim; if Host.IsEmpty then begin ShowMessage('Wrong host value!'); Exit; end; if not TryStrToInt(SetupForm.EditPort.Text, Port) then begin ShowMessage('Wrong port value!'); Exit; end; Msg := Edit1.Text; TTask.Run( procedure var MemoryStream: TMemoryStream; StringStream: TStringStream; JObj: TJSONObject; begin try StringStream := nil; try JObj := TJSONObject.Create; try MemoryStream := TMemoryStream.Create; try MemoryStream.LoadFromFile(FImageFilePath); JObj.AddPair('image_encoded', TIdEncoderMIME.EncodeStream(MemoryStream)); finally MemoryStream.Free; end; JObj.AddPair('message', Msg); StringStream := TStringStream.Create(JObj.ToJSON, TEncoding.UTF8); finally JObj.Free; end; if IdTCPClient1.Connected then IdTCPClient1.Disconnect; IdTCPClient1.Host := Host; IdTCPClient1.Port := Port; try IdTCPClient1.Connect; except TThread.Queue(nil, procedure begin ShowMessage('Connection error!'); end); Exit; end; try try IdTCPClient1.IOHandler.LargeStream := False; IdTCPClient1.IOHandler.Write(StringStream, 0, True); finally IdTCPClient1.Disconnect; end; except TThread.Queue(nil, procedure begin ShowMessage('Send error!'); end); Exit; end; finally StringStream.Free; end; TThread.Queue(nil, procedure begin ShowMessage('BASE64 Image and message successfully sent to server!'); end); except on E: Exception do begin TThread.Queue(nil, procedure begin ShowMessage('Error! ' + E.Message); end); end; end; end); end; procedure TMainForm.IdTCPServer1Execute(AContext: TIdContext); var JSON: TJSONObject; MemoryStream: TMemoryStream; StringStream: TStringStream; // alternatively: // JSONStr: string; Msg: String; begin MemoryStream := nil; try StringStream := TStringStream.Create('', TEncoding.UTF8); try AContext.Connection.IOHandler.LargeStream := False; AContext.Connection.IOHandler.ReadStream(StringStream, -1, False); JSON := TJSONObject.ParseJSONValue(StringStream.DataString) as TJSONObject; finally StringStream.Free; end; // alternatively: // JSONStr := AContext.Connection.IOHandler.ReadString(AContext.Connection.IOHandler.ReadInt32); // JSON := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject; try MemoryStream := TMemoryStream.Create; TIdDecoderMIME.DecodeStream(JSON.GetValue('image_encoded').Value, MemoryStream); Msg := JSON.GetValue('message').Value; finally Json.Free; end; MemoryStream.Position := 0; TThread.Synchronize(nil, procedure begin Edit2.Text := Msg; Image1.Bitmap.LoadFromStream(MemoryStream); end); finally MemoryStream.Free; end; TThread.Synchronize(nil, SaveImageToDisk); end; Edited November 22 by Remy Lebeau 1 Share this post Link to post
KimHJ 3 Posted November 21 Remy, It works as it is, but I changed the code in the Client StringStream := TStringStream.Create(IdEncoderMIME1.Encode(MemoryStream),TEncoding.UTF8); .. IdTCPClient1.Socket.Write(JObj.ToJSON); I change the code in the Server code: StringStream := TStringStream.Create('', TEncoding.UTF8); I tried to use ReadString, but the I get a read error when it tries to parse the json PicStr: String; .. PicStr := AContext.Connection.IOHandler.ReadString(-1); JSON := TJSONObject.ParseJSONValue(PicStr) as TJSONObject; It works fine now and I found that it did save the images, but they was not in the folder that I had in the filename string, but in the project folder where the application was running from. I take a picture with the camera and place it in a TImage. procedure TMainForm.TakePhotoFromCameraAction1DidFinishTaking(Image: TBitmap); begin Image1.Bitmap.Assign(Image); end; Then with the same code that works sending a image from a file works. MemoryStream := TMemoryStream.Create; MemoryStream.Position := 0; MemoryStream.LoadFromFile(FImageFilePath); When I try this I get a blank image. MemoryStream := TMemoryStream.Create; MemoryStream.Position := 0; Image1.Bitmap.SaveToStream(MemoryStream); Thanks for your help. Share this post Link to post
Remy Lebeau 1435 Posted November 21 (edited) 21 minutes ago, KimHJ said: It works as it is There is no possible way it could have worked before the way you showed it earlier. That code had logic bugs in the communications. 21 minutes ago, KimHJ said: I changed the code in the Client IdTCPClient1.Socket.Write(JObj.ToJSON); ... I tried to use ReadString, but the I get a read error when it tries to parse the json PicStr: String; .. PicStr := AContext.Connection.IOHandler.ReadString(-1); JSON := TJSONObject.ParseJSONValue(PicStr) as TJSONObject; That is because you are using ReadString() incorrectly. You are writing the JSON as a string without sending either a leading size or a terminator, so the server will have no way to know when the string actually ends. But you are not telling ReadString() how many bytes to read, and when the ABytes parameter is <= 0 then ReadString() will just return a blank string since it has nothing to read. Hence the parse error. The examples I gave you in my previous reply 1) use WriteLn() and ReadLn() to send/read the JSON with a terminating CRLF, and 2) use Write(TStream) and ReadStream/ReadString() to send/read the JSON with a leading size. 21 minutes ago, KimHJ said: I found that it did save the images, but they was not in the folder that I had in the filename string, but in the project folder where the application was running from. Makes sense, because SaveImageToDisk() uses a relative file path instead of an absolute file path. Edited November 21 by Remy Lebeau Share this post Link to post
KimHJ 3 Posted November 22 Remy, I'm sorry I didn't see the sample codes you wrote, before. Maybe we where posting at the same time. On the server side in the first example I get an error: First chance exception at $00007FFCF5F3FA4C. Exception class EIdReadLnMaxLineLengthExceeded with message 'Max line length exceeded.'. Process CSIPictureServer.exe (3744) Here: JsonStr := AContext.Connection.IOHandler.ReadLn; The second example worked, thanks. I have the TImage with a image, why when I save to MemoryStream like this I just get a 19kb black image on the server side? Image1.Bitmap.SaveToStream(MemoryStream); //MemoryStream.LoadFromFile(FImageFilePath); I just get a 19kb black image. Share this post Link to post
Remy Lebeau 1435 Posted November 22 (edited) 58 minutes ago, KimHJ said: On the server side in the first example I get an error: First chance exception at $00007FFCF5F3FA4C. Exception class EIdReadLnMaxLineLengthExceeded with message 'Max line length exceeded.'. Process CSIPictureServer.exe (3744) How big is your JSON? The IOHandler.MaxLineLength property is 16K characters by default. You can set it to a larger value if needed, such as MaxInt. I have now updated my earlier example to show that. Quote The second example worked, thanks. OK. Quote I have the TImage with a image, why when I save to MemoryStream like this I just get a 19kb black image on the server side? Image1.Bitmap.SaveToStream(MemoryStream); //MemoryStream.LoadFromFile(FImageFilePath); I just get a 19kb black image. Make sure you synchronize that code so it runs in the main UI thread and not in your worker thread. Edited November 22 by Remy Lebeau Share this post Link to post
KimHJ 3 Posted November 22 (edited) I just replaced it in the same place in the Client where the MemoryStream.LoadFromFile(FImageFilePath); was. I can save it first and then use LoadFromFile and it works, but I don't want to save them on the device. Image1.Bitmap.SaveToFile(FImageFilePath); MemoryStream.LoadFromFile(FImageFilePath); Edited November 22 by KimHJ Share this post Link to post
KimHJ 3 Posted November 22 I was just thinking I can just delete the image after I have send it. Share this post Link to post
Remy Lebeau 1435 Posted November 22 (edited) 1 hour ago, KimHJ said: I just replaced it in the same place in the Client where the MemoryStream.LoadFromFile(FImageFilePath); was. That's fine, but you need to synchronize it with the main thread snce you're accessing a UI control. //MemoryStream.LoadFromFile(FImageFilePath); TThread.Synchronize(nil, procedure begin Image1.Bitmap.SaveToStream(MemoryStream); end); The alternative is to create the MemoryStream in the main thread and save the bitmap into it, then pass the MemoryStream to your worker thread for sending. Edited November 22 by Remy Lebeau Share this post Link to post