TStream vs TFileStream - Header pliku

0

Napotkałem na ciekawą rzecz spowodowaną prawdopodobnie brakiem wiedzy z mojej strony :/

uses PNGImage;

procedure test;
var
  Picture: TPicture;
  PNGObject: TPNGObject;
  MStream : TMemoryStream;
  FStream : TFileStream;
begin
  // Ładowanie PNG 
  FStream := TFileStream.Create('c:\plik.png',fmOpenRead); 

  MStream := TMemoryStream.Create;
  Picture := TPictureCreate;
  Picture.LoadFromFile('c:\plik.png');
  Picture.Graphic.SaveToStream(MStream);
  
  //Dla pewności
  MStream.SaveToFile('c:\nowy.bmp');  //Mimo iż rozszerzenie bmp to na dysku zapisał się plik PNG tyle że o rozszeżeniu BMP

  //PNG 
  PNGObject := TPngObject.Create;
  PNGObject.LoadFromStream(FStream);   //To się wykonuje prawidłowo
  PNGObject.LoadFromStream(MStream);   //To wywala błąd
end;

Błąd spowodowany jest tym kodem:

{Loads the image from a stream of data}
procedure TPngImage.LoadFromStream(Stream: TStream);
var
  Header    : Array[0..7] of AnsiChar;
  HasIDAT   : Boolean;

  {Chunks reading}
  ChunkCount : Cardinal;
  ChunkLength: Cardinal;
  ChunkName  : TChunkName;
begin
  {Initialize before start loading chunks}
  ChunkCount := 0;
  ClearChunks();
  {Reads the header}
  Stream.Read(Header[0], 8);

  {Test if the header matches}
  if Header <> PngHeader then  //<---   Header jest pusty
  begin
    RaiseError(EPNGInvalidFileHeader, EPNGInvalidFileHeaderText);
    Exit;
  end;

Jak rozwiązać problem aby MStream zostało załadowane przez PNGObject?

0

Mimo iż rozszerzenie bmp to na dysku zapisał się plik PNG tyle że o rozszeżeniu BMP
Przecież dane trzymane w tym MStream to plik PNG - to, że sobie zmienisz docelowe rozszerzenie nie ma żadnego wpływu na format danych wewnątrz strumienia.

Jak rozwiązać problem aby MStream zostało załadowane przez PNGObject?
TMemoryStream dziedziczy po TStream, którego to instancję przyjmuje LoadFromStream - zatem, o ile poprawnie wczytujesz dane do MStream, bezproblemowo powinny zostać one zrozumiane przez TPngImage.LoadFromStream.

PS żyjemy w erze terabajtowych dysków twardych - nazywaj zmienne porządnie. MemStream, FileStream i od razu wiadomo, co z czym, a nie jakieś MStreamy.

0
Patryk27 napisał(a):

Mimo iż rozszerzenie bmp to na dysku zapisał się plik PNG tyle że o rozszeżeniu BMP
Przecież dane trzymane w tym MStream to plik PNG - to, że sobie zmienisz docelowe rozszerzenie nie ma żadnego wpływu na format danych wewnątrz strumienia.

To doskonale wiem dlatego oznaczyłem tą linię jako **//Dla pewności ** że MStream jest prawidłowy bo plik png zapisał się na dysku prawidłowo pod nową nazwą.

TMemoryStream dziedziczy po TStream, którego to instancję przyjmuje LoadFromStream - zatem, o ile poprawnie wczytujesz dane do MStream, bezproblemowo powinny zostać one zrozumiane przez TPngImage.LoadFromStream.

Tak powinno być ale jednak nie jest dlatego napisałem ten post.

0

Tak powinno być ale jednak nie jest dlatego napisałem ten post.
Zatem pokaż pełny kod, jak to wczytujesz.

4

Przed LoadFromStreamwstaw:

  MStream.Position:= 0;
0

Oto kod prezentujący problem.

procedure TForm1.Button1Click(Sender: TObject);
var
  Picture: TPicture;
  PNGObject: TPNGObject;
  MStream : TMemoryStream;
  FStream : TFileStream;
begin
  // Ładowanie PNG
  MStream := TMemoryStream.Create;
  Picture := TPicture.Create;
  Picture.LoadFromFile('c:\plik.png');
  Picture.Graphic.SaveToStream(MStream);
  Picture.Free;

  FStream := TFileStream.Create('c:\plik.png',fmOpenRead);


  //Dla pewności
  MStream.SaveToFile('c:\nowy.png');  //Mimo iż rozszerzenie bmp to na dysku zapisał się plik PNG tyle że o rozszeżeniu BMP

  //PNG
  PNGObject := TPngObject.Create;
  PNGObject.LoadFromStream(FStream);   //To się wykonuje prawidłowo
  PNGObject.LoadFromStream(MStream);   //To wywala błąd
end;
0

Znalazłem problem.

  Picture.Graphic.SaveToStream(MStream);
  Stream.Seek(0,soBeginning);  <- Tego brakowało

Działa tylko z TMemoryStream jeżeli MStream zamienimy na TStream to znowu błąd

Swoją drogą dziwne że klasa TCustomMemoryStream domyślnie nie ustawia FPosition na 0.

2

co w tym dziwnego? Stream to strumień, może to być na przykład fizyczny zapis na nośnik magnetyczny, który fizycznie trzeba przewinąć gdy użyjesz Seek. Może to być też strumień sieciowy który już został wysłany i nie da się go przewinąć. Zapis nie może założyć że strumień jest przewijalny i go automatycznie wrócić na początek

0

Pozycja kursora strumieni zawsze jest automatycznie przesuwana, czy to po odczycie danych ze strumienia, czy przy zapisie danych do niego; Jest to jedyne poprawne rozwiązanie i nie może być od niego odstępstw, bo obsługa strumieni stanie się potwornie toporna;
____Używam w swoim programie plików amorficznych; Zawierają one różne dane - pojedyncze liczby i wartości logiczne, macierze liczb, a także obrazy PNG; Załadowanie całego pliku do pamięci to utworzenie wielu obiektów, z czego każdy dostaje na wejściu referencję do strumienia źródłowego i odczytuje jego zadany fragment (wczytuje kawałek danych dla siebie); Pojedynczy obiekt wie ile danych wczytać - np. najpierw jakieś liczby/macierz liczb, na końcu obraz PNG;

Do czego zmierzam - obiekt ładuje dane ze strumienia w prosty sposób, najpierw odczytuje kilka liczb, następnie od razu całe macierze liczb, na końcu od razu obraz; Teraz wyobraź sobie co bym musiał robić, gdyby wczytanie danych nie było równoznaczne z przesuwaniem kursora; Po każdym odczycie musiałbym sam ten kursor przesuwać, co przy obrazie PNG było by kłopotliwe, gdyż one w strumieniu mają różne rozmiary i ich określenie było by trudne; A tak to strumień sam kursor przesuwa, więc tego problemu nie ma - nie muszę nic liczyć i notorycznie ręcznie przesuwać kursor;

Rozwiązanie podał Ci @kAzek - jeżeli zapisujesz dane do strumienia, kursor zostaje przesunięty na koniec zapisanych danych, więc przed odczytem z tego strumienia musisz go sam przesunąć; Możesz użyć metody Seek do przesunięcia kursora na początek lub koniec danych, możesz też użyć właściwości Position - ta umożliwia ustawienie kursora w dowolnym miejscu, przed dowolnym bajtem danych;

Działa tylko z TMemoryStream jeżeli MStream zamienimy na TStream to znowu błąd

Nie możesz prawidłowo utworzyć i wykorzystać obiektu klasy TStream, bo jest to klasa bazowa (abstrakcyjna) dla wszystkich klas strumieni (TMemoryStream, TStringStream, TFileStream itd.); Tak samo nie możesz normalnie używać instancji klasy TStrings; Mam na myśli zwykłe utworzenie obiektu, bo klasę bazową można wykorzystać np. do deklaracji parametru metody; Ale to już na osobny temat, bo to obszerny temat;

Niektóre bazowe klasy mają metody abstrakcyjne, niektóre metody wirtualne z pustym ciałem, jeszcze inne rzucają wyjątkami w takich metodach wirtualnych; Bazowe klasy różnią się budową, jednak z reguły nie używa się ich instancji;


Złoty Lew napisał(a)

Może to być też strumień sieciowy który już został wysłany i nie da się go przewinąć.

O ile wysłanie danych nie jest równoznaczne z wyczyszczeniem i/lub zwolnieniem strumienia z pamięci to nie widzę powodu, aby nie dało się go przewinąć.

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