Jump to content
KimHJ

Unable to save from TImage to file

Recommended Posts

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
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 by Remy Lebeau

Share this post


Link to post

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
33 minutes 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().

33 minutes 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
  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 by Remy Lebeau
  • Thanks 1

Share this post


Link to post

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
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 by Remy Lebeau

Share this post


Link to post

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×