Wysłanie bitmapy w JSON

0

Witam,

Tworzę aplikację z serwerem REST oraz klient Android w Delphi XE10.1 i mam problem, a mianowicie - nie mogę poradzić sobie z wysyłaniem obrazków...

Mój kod:

var
    AImageStream: TStream;
begin
// Dodawanie zdjęć
// Listbox lbAddVIN zawiera itemy ktore zawieraja obrazek (lbAddVIN_Photos.ItemByIndex(i).ItemData.Bitmap)
for i := 0 to lbAddVIN_Photos.Items.Count -1 do
begin
  AImageStream := TMemoryStream.Create;
  try
    AImageStream.Position := 0;
    lbAddVIN_Photos.ItemByIndex(i).ItemData.Bitmap.SaveToStream(AImageStream);

    LJSONObject.AddPair(TJSONPair.Create ('ZDJECIE_' + IntToStr(i + 1),
                        TIdEncoderMIME.EncodeStream( AImageStream )));
    // LJSONObject.ToString - zwraca PUSTY STRING 

    // Tutaj rozmiar AImageStream.Size > 0
    AImageStream.Free;

    AImageStream := TMemoryStream.Create;
    TIdDecoderMIME.DecodeStream((LJSONObject.GetValue('ZDJECIE_1') as TJSONString).Value, AImageStream);
    AImageStream.Position := 0;

    // Tutaj juz AImageStream.Size = 0

    image1.Bitmap.LoadFromStream(AImageStream);

  finally
    AImageStream.Free;
  end;	

Co robię nie tak? Nie mogę nic na necie znaleźć odnośnie wysyłania Image przez JSON.

Podobno jest to najszybszy sposób z JSONem poprzez kodowanie go, przez co skraca jego rozmiar.

1

u mnie działa

procedure TForm1.btn1Click(Sender: TObject);
var
  fs: TFileStream;
  json: TJSONObject;
  s, value: string;
begin
  fs := TFileStream.Create('d:\download\infile.jpg', fmOpenRead);
  try
    fs.Position := 0;
    value := TIdEncoderMIME.EncodeStream(fs);
  finally
    fs.Free;
  end;

  fs := TFileStream.Create('d:\download\outfile.b64', fmCreate);
  try
    fs.Write(Pointer(value)^, Length(value));
  finally
    fs.Free;
  end;

  json := TJSONObject.Create;
  fs := TFileStream.Create('d:\download\outfile.json', fmCreate);
  try
    json.AddPair('image', value);
    s := json.ToJSON;
    fs.Write(Pointer(s)^, Length(s));
  finally
    json.Free;
    fs.Free;
  end;
end;

w załączniku pliki in i out

0

Ja nie korzystam z FileStream, bo w Firemonkey i aplikacji mobilnej zaczytując zdjęcia z telefonu lub bezpośrednio z aparatu nie ma dostępu do ścieżki pliku, a zwracany jest jedynie TBitmap.

Jak zrobisz to samo na TMemoryStream to też Ci zadziała?

Nawet

AImageStream := TMemoryStream.Create;
AImageStream.Position := 0;
lbAddVIN_Photos.ItemByIndex(i).ItemData.Bitmap.SaveToStream(AImageStream);
s := TIdEncoderMIME.EncodeStream( AImageStream );
showmessage(s);

Zwraca pusty komunikat...

No tak... problem polegał w tym, że AImageStream.Position leżał nie w tym miejscu. Powinien po SaveToStream.

1

masz najprostszą wersję

procedure TForm1.btn1Click(Sender: TObject);
const
  pairName = 'image';
var
  ms: TMemoryStream;
  json: TJSONObject;
  s, value: string;
begin
  ms := TMemoryStream.Create;
  ms.LoadFromFile('d:\download\infile.jpg');
  ms.Position := 0;
  json := TJSONObject.Create;
  value := TIdEncoderMIME.EncodeStream(ms);
  json.AddPair(pairName, value);
  s := json.GetValue(pairName).Value;
  ms.Size := 0;
  TIdDecoderMIME.DecodeStream(s, ms);
  ms.SaveToFile('d:\download\outfile.jpg');
end;

infile:
infile.jpg

outfile:
outfile.jpg

0

To teraz dostaję komunikat "Max Line Exceed".

Bo string jest za długi. Co w takim wypadku?

0

Naucz się, że po pierwsze DAJESZ KOD a po drugie WSKAZUJESZ, KTÓRA LINIA POWODUJE BŁĄD.

zamień

function EncodeStream(ASrcStream: TStream; const ABytes: Integer = -1): string;

na któreś z tych

procedure EncodeStream(ASrcStream: TStream; ADestStrings: TStrings; const ABytes: Integer = -1); 
procedure EncodeStream(ASrcStream: TStream; ADestStream: TStream; const ABytes: Integer = -1);
0

OK, rzeczywiście kodu nie wstawiłem - uzupełnię po pracy.
Mógłbyś wytłumaczyć jaki cel jest w tej zamianie i gdzie to zamienić? Chodzi o podmianę w pliku źródłowym gdzie znajduje się ta funkcja?

PS. Z tego co pamiętam wywala się na EXECUTE w procedurze, która wysyła tego JSONa przez komponenty REST [DSRestRequest bodajże]. - kod podeślę po 16

0

skoro się wywala, że próbujesz za dużo wcisnąć do stringa to trzeba zrobić coś, żeby nie zapisywać do stringa a do czegoś "pojemniejszego"

0

Tylko jak to wysłać przez REST...

0

Przekazanie parametru JSON:

ClientModule1.ServerMethods1Client.AddVIN(LJSONObject.ToString);
FAddVINCommand: TDSRestCommand;
function TServerMethods1Client.AddVIN(AJSON: string; const ARequestFilter: string): Boolean;
begin
  if FAddVINCommand = nil then
  begin
    FAddVINCommand := FConnection.CreateCommand;
    FAddVINCommand.RequestType := 'GET';
    FAddVINCommand.Text := 'TServerMethods1.AddVIN';
    FAddVINCommand.Prepare(TServerMethods1_AddVIN);
  end;
  FAddVINCommand.Parameters[0].Value.SetWideString(AJSON);
  FAddVINCommand.Execute(ARequestFilter); // Tutaj wyjątek
  Result := FAddVINCommand.Parameters[1].Value.GetBoolean;
end;

Jak zapisałem AJSON do .txt to jego rozmiar wynosi 500kb.

0

Treść wyjątku

Max line Length Exceed
0

Wszystko wskazuje na to, że to dlatego, że cały ciąg znaków jest w jednej linii.. Jak by to rozdzielić na kilka?

Czy konwersja z TBitmap do JPEG coś zmieni? Na pewno zmieni się rozmiar z 500 kb do iluś, a to z koleii wpłynie na mniejszą ilość znaków w stringu.

Znalazłem coś takiego:
http://stackoverflow.com/questions/29268863/how-to-deal-with-maxlinelengthexceeded-with-indy-tidtcpclient

0

Czy konwersja z TBitmap do JPEG coś zmieni?

Na pewno jakość obrazu (stratnie) i jego rozmiar w postaci skompresowanej;

Niewiele gorszy efekt możesz uzyskać za pomocą PNG - rozmiar też będzie mały, ale nie stracisz jakości; Przy czym im mniej pstrokaty obraz, tym lepsza kompresja.

0

Niestety wszystkie przysyłane obrazy idą do bazy do kolumny o typie BLOB, idąc w TBitmap, baza nieziemsko spuchnie...

0

A jest jakiś sensowny powód, aby używać bitmap? Bo jeśli nie - użyj innego formatu.

0

Chwila, a czy przypadkiem json to nie format printowalny?
A taka bitmapa może zawierać bity nie printowalne, ja to bym rozdzielił bitmapę na jej nagłówki i image data.

Zakodował jakoś printowalnie np. base64 i ewentualnie image data podzielił na więcej części.

A po drugiej stronie odebrał, odkodował i zlepił w całość.

0

To o czym napisałeś to dla mnie czarna magia...

Wyczytałem, że prostszym sposobem jest ustawienie w TIdIOHandler.MaxLineLength = MaxInt.
Ale ktoś może mi powiedzieć jak dostać się do tej właściwości przez serwer REST?

unit uServerContainer;

interface

uses System.SysUtils, System.Classes,
    Datasnap.DSHTTPCommon, Datasnap.DSHTTP,
  Datasnap.DSServer, Datasnap.DSCommonServer,
  IPPeerServer, IPPeerAPI, Datasnap.DSAuth;

type
  TServerContainer1 = class(TDataModule)
    DSServer1: TDSServer;
    DSHTTPService1: TDSHTTPService;
    DSAuthenticationManager1: TDSAuthenticationManager;
    DSServerClass1: TDSServerClass;
    procedure DSServerClass1GetClass(DSServerClass: TDSServerClass;
      var PersistentClass: TPersistentClass);
    procedure DSAuthenticationManager1UserAuthorize(Sender: TObject;
      EventObject: TDSAuthorizeEventObject; var valid: Boolean);
    procedure DSAuthenticationManager1UserAuthenticate(Sender: TObject;
      const Protocol, Context, User, Password: string; var valid: Boolean;
      UserRoles: TStrings);
  private
    { Private declarations }
  public
  end;

var
  ServerContainer1: TServerContainer1;

implementation

{%CLASSGROUP 'FMX.Controls.TControl'}

{$R *.dfm}

uses
  uServerMethods;

procedure TServerContainer1.DSServerClass1GetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := uServerMethods.TServerMethods1;
end;

procedure TServerContainer1.DSAuthenticationManager1UserAuthenticate(
  Sender: TObject; const Protocol, Context, User, Password: string;
  var valid: Boolean; UserRoles: TStrings);
begin
  { TODO : Validate the client user and password.
    If role-based authorization is needed, add role names to the UserRoles parameter  }
  valid := True;
end;

procedure TServerContainer1.DSAuthenticationManager1UserAuthorize(
  Sender: TObject; EventObject: TDSAuthorizeEventObject;
  var valid: Boolean);
begin
  { TODO : Authorize a user to execute a method.
    Use values from EventObject such as UserName, UserRoles, AuthorizedRoles and DeniedRoles.
    Use DSAuthenticationManager1.Roles to define Authorized and Denied roles
    for particular server methods. }
  valid := True;
end;

end.
0

Do base64 spróbuj zamiast Indy użyć http://docwiki.embarcadero.com/Libraries/Berlin/en/System.NetEncoding.TBase64Encoding nie twierdzę że to pomoże ale może warto sprawdzić.

0

jakaś masakra...

wystarczy użyć procedure EncodeStream(ASrcStream: TStream; ADestStrings: TStrings; const ABytes: Integer = -1); o czym wcześniej pisałem i potem do JSONa najpierw dodać liczbę linii a potem poszczególne linie. Można też spróbować wysłać od razu ADestStrings.Text ale jest duża szansa, że przy parsowaniu się wysypie.

0

Użyłem funkcji

TIdEncoderMIME.EncodeStream( AImageStream, AImageStrings );

ale wciąż mam 1 linijke w TStringList, która zawiera cały zakodowany tekst

0

Udało mi się rozwiązać problem. Dzięki za pomoc!

0

Udało mi się rozwiązać problem. Dzięki za pomoc!

Ale jak to zrobiłeś?

1 użytkowników online, w tym zalogowanych: 0, gości: 1