CYFROWY BARON • PROGRAMOWANIE • Zobacz wątek - Gdiplus::Image - Zapis do pliku
Strona 1 z 2

Gdiplus::Image - Zapis do pliku

Nowy postNapisane: czwartek, 27 września 2012, 12:06
przez Mironas
Witam,

Obiekt Gdiplus::Image* img wczytany (utworzony) z pliku.

Wykonuję na nim jakieś zmiany i próbuję zapisać je do TEGO SAMEGO pliku.
image->Save(...)
Zwraca błąd 'Win32Error'.
Jak się domyślam plik jest otwarty tylko do odczytu i dopóki nie zniszczę img to nie mogę do niego pisać. Ale niszczenie img przed zapisem jest bez sensu.

Jedyne co wymyśliłem to utworzyć bitmapę (bmp) z kopią img, zniszczyć img i zapisać bmp do pliku (przykład poniżej). Jednak musiałbym też przepisać wszystkie metadane (zależy mi na nich) a to jeszcze bardziej skomplikuje zapis.

Czy jest prostszy sposób na zapisanie img do tego samego pliku z którego został utworzony?

Przykład zapisu z wykorzystaniem bitmapy:
KOD cpp:     UKRYJ  
void __fastcall TForm1::ToolButton1Click(TObject *Sender)
{
  String plik = "C:\\Plik.jpg";
  Gdiplus::Status status;
  CLSID clsid;

  Gdiplus::Image* img = new Gdiplus::Image(plik.w_str());

  // tu wykonuję modyfikacje na 'image1'

  // przepisanie img >> bmp
  Gdiplus::Bitmap* bmp = new Gdiplus::Bitmap(img->GetWidth(), img->GetHeight(), PixelFormat32bppARGB);
  GGraphics graf (bmp);
  graf.DrawImage(img, 0, 0);

  // niszczenie img
  delete img;

  // zapis do pliku
  GetEncoderClsid(L"image/jpeg", &clsid);
  status = bmp->Save(plik.w_str(), &clsid);
  if (status)
    Beep();

  delete bmp;
}
 

PS
Utworzenie nowego obiektu przez Gdiplus::Image* img2 = img->Clone() i zniszczenie img nic nie daje ponieważ nie da się pisać do pliku zanim nie usunie się img2 :( .

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: czwartek, 27 września 2012, 16:52
przez Cyfrowy Baron
Nie wiem co robisz źle, ale u mnie to działa:

Plik nagłówkowy np. Unit1.h
KOD cpp:     UKRYJ  
#include "gdiplus.h"
private:
                Gdiplus::GdiplusStartupInput gdiplusStartupInput;
                ULONG_PTR gdiplusToken;


Plik źródłowy np. Unit1.cpp
KOD cpp:     UKRYJ  
#pragma link "gdiplus.lib"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
//---------------------------------------------------------------------------
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
 unsigned int num = 0;
 unsigned int size = 0;

 Gdiplus::GetImageEncodersSize(&num, &size);
 if(size == 0)return -1;

 Gdiplus::ImageCodecInfo* imageCodecInfo = new Gdiplus::ImageCodecInfo[size];
 Gdiplus::GetImageEncoders(num, size, imageCodecInfo);

 for(unsigned int i = 0; i < num; ++i)
 {
  if(wcscmp(imageCodecInfo[i].MimeType, format) == 0)
  {
   *pClsid = imageCodecInfo[i].Clsid;
   delete[] imageCodecInfo;
   return i;
  }
 }
 delete[] imageCodecInfo;
 return -1;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button11Click(TObject *Sender)
{
 Gdiplus::Graphics graphics(this->Handle);
 String sFile = "c:\\plik.jpg";
 Gdiplus::Image imageFile( sFile.c_str(), false );

 /* Modyfikacja obrazu - obrócenie o 90 stopni */
 imageFile.RotateFlip(Gdiplus::Rotate90FlipNone);

 CLSID fClsid;
 GetEncoderClsid(L"image/jpeg", &fClsid);

 imageFile. Save(sFile.c_str(), &fClsid, NULL);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
 Gdiplus::GdiplusShutdown(gdiplusToken);
}


Dlaczego tak:

Mironas napisał(a):
KOD cpp:     UKRYJ  
Gdiplus::Image* img = new Gdiplus::Image(plik.w_str());


Skoro to nie jest obiekt globalny lecz lokalny?
Oczywiście z obiektem prywatnym lub publicznym też działa prawidłowo:

Plik nagłówkowy np. Unit1.h
KOD cpp:     UKRYJ  
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;

        Gdiplus::Image *imageFile;


Plik źródłowy np. Unit1.cpp
KOD cpp:     UKRYJ  
void __fastcall TForm1::Button11Click(TObject *Sender)
{
 Gdiplus::Graphics graphics(this->Handle);
 String sFile = "c:\\plik.jpg";
 imageFile = new Gdiplus::Image( sFile.c_str() );

 /* Modyfikacja obrazu - obrócenie o 90 stopni */
 imageFile->RotateFlip(Gdiplus::Rotate90FlipNone);

 CLSID fClsid;
 GetEncoderClsid(L"image/jpeg", &fClsid);

 imageFile->Save(sFile.c_str(), &fClsid, NULL);
}


Wypróbuj ten kod. Jeżeli u Ciebie nie zadziała to będzie oznaczać, że to wina bibliotek środowiska XE.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: czwartek, 27 września 2012, 18:42
przez Mironas
Faktycznie - twój przykład u mnie działa, ale wystarczy usunąć RotateFlip i już nie chce się zapisać. Oczywiście nie ma sensu zapisywać niezmodyfikowanego pliku, ale w moim przypadku modyfikacje polegają zmianie metadanych. A niestety po takiej zmianie nadal nie chce się zapisać.
Przykład:
KOD cpp:     UKRYJ  
void __fastcall TForm1::ToolButton2Click(TObject *Sender)
{
  String sFile = "C:\\Plik01.png";
  Gdiplus::Image imageFile(sFile.c_str());

  // dodaj metadane
  AnsiString sKomentarz = "Ala ma kota.";
  Gdiplus::PropertyItem* item = new Gdiplus::PropertyItem();
  item->id = PropertyTagExifUserComment;
  item->length = sKomentarz.Length()+1;         // length + null_terminator
  item->type = PropertyTagTypeASCII;
  item->value = sKomentarz.c_str();
  imageFile.SetPropertyItem(item);
  delete item;

  CLSID fClsid;
  GetEncoderClsid(L"image/png", &fClsid);

  //sFile = "C:\\Plik02.png";   // zapisanie pod inną nazwą
  Gdiplus::Status status = imageFile.Save(sFile.c_str(), &fClsid);
  if ( status )
    Beep();
}
 


Procedura zapisania komentarza do pliku działa poprawnie na plikach PNG co można sprawdzić zapisując plik pod inną nazwą i odczytując zapisany komentarz taką funkcją:
KOD cpp:     UKRYJ  
void __fastcall TForm1::ToolButton4Click(TObject *Sender)
{
  String sFile = "C:\\Plik02.png";
  Gdiplus::Image imageFile(sFile.c_str());

  // odczyt komentarza
  UINT size = imageFile.GetPropertyItemSize(PropertyTagExifUserComment);
  if ( size )
  {
    Gdiplus::PropertyItem* item = (Gdiplus::PropertyItem*)malloc(size);
    imageFile.GetPropertyItem(PropertyTagExifUserComment, size, item);
    AnsiString sKomentarz = (char*)item->value;
    ShowMessage(sKomentarz);
    free(item);
  }
}
 

UWAGA - zapisywanie i odczytywanie komentarza tymi funkcjami nie działa u mnie poprawnie na plikach JPG, ale na moje potrzeby wystarczy PNG.

Dlaczego tak:
Gdiplus::Image* img = new Gdiplus::Image(plik.w_str());
Skoro to nie jest obiekt globalny lecz lokalny?

Wiem. U mnie w programie IMG jest globalne, i tylko tak szybko poskładałem aby zrobić jedną funkcję demonstrującą problem.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: czwartek, 27 września 2012, 19:50
przez Cyfrowy Baron
Nie wiem czemu, ale jeżeli nie zmodyfikujesz zawartości pliku, nie da się go zapisać, ale można to rozwiązać bez konieczności przerysowywania grafiki:

KOD cpp:     UKRYJ  
void __fastcall TForm1::Button11Click(TObject *Sender)
{
 Gdiplus::Status status;
 Gdiplus::EncoderParameters encoderParameters;
 CLSID fClsid;
 ULONG quality;


 String sFile = "c:\\plik.jpg";
 String sTemp = "c:\\~plik.jpg";

 if(imageFile == NULL && FileExists(sFile) )
  imageFile = new Gdiplus::Image( sFile.c_str(), FALSE );

 AnsiString sKomentarz = "Ala ma kota.";
 Gdiplus::PropertyItem* item = new Gdiplus::PropertyItem();
 item->id = PropertyTagExifUserComment;
 item->length = sKomentarz.Length()+1;         // length + null_terminator
 item->type = PropertyTagTypeASCII;
 item->value = sKomentarz.c_str();
 imageFile->SetPropertyItem(item);
 delete item;

 GetEncoderClsid(L"image/jpeg", &fClsid);

 /* przykład ustawiania stopnia kompresji pliku */
 encoderParameters.Count = 1;
 encoderParameters.Parameter[0].Guid = Gdiplus::EncoderQuality;
 encoderParameters.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
 encoderParameters.Parameter[0].NumberOfValues = 1;
 quality = 100;
 encoderParameters.Parameter[0].Value = &quality;
 /* koniec przykładu */

 status = imageFile->Save( sTemp.c_str(), &fClsid, &encoderParameters); /* zapisywanie do pliku tymczasowego */

 if (status)
 {
        Beep();
 }

 delete imageFile;

 if(!DeleteFile(sFile)) ShowMessage("Nie można usunąć pliku!"); /* usuwanie oryginalnego pliku */
 if(!RenameFile(sTemp, sFile)) ShowMessage("Błąd"); /* zmiana nazwy pliku tymczasowego na oryginalny */
}


Będę szukał, ale póki co to jest chyba najlepsze rozwiązanie. :roll:

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 09:26
przez Mironas
Dzięki za pomysł. Jest to jakieś rozwiązanie chyba szybsze w działaniu niż obracanie zawartości.
Chociaż nadal nie rozumiem dlaczego nie mogę po prostu zapisać pliku. Może ktoś ma pomysł jak oszukać IMAGE że niby jest zmieniony?

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 09:52
przez polymorphism
Zwraca błąd 'Win32Error'.

Sprawdź, co zwraca funkcja GetLastError po wystąpieniu tego błędu.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 10:13
przez Mironas
32
ERROR_SHARING_VIOLATION
Proces nie może uzyskać dostępu do pliku, ponieważ jest on używany przez inny proces.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 11:15
przez polymorphism
W dokumentacji masz jak wół napisane:

    GDI+ does not allow you to save an image to the same file that you used to construct the image. The following code creates an Image object by passing the file name MyImage.jpg to an Image constructor. That same file name is passed to the Image::Save method of the Image object, so the Image::Save method fails.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 11:33
przez Mironas
No właśnie szukamy sposobu aby to ominąć i jednak zapisać (najmniej kosztownym sposobem) obraz do tego samego pliku.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 12:24
przez polymorphism
Obawiam się, że bez robienia kopii obrazka się nie obejdzie. Spróbuj tak:
  1. załaduj obrazek do Image.
  2. zmień co tam masz zmienić.
  3. zapisz bitmapę z Image'a do strumienia pamięciowego (IStream).
  4. usuń obiekt Image.
  5. zapisz strumień pamięciowy do pliku.

Choć prościej by było po prostu zapisać obrazek do pliku pod inna nazwą, usunąć oryginalny plik i zmienić nazwę kopii na nazwę oryginału.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 18:37
przez Cyfrowy Baron
polymorphism napisał(a):Spróbuj tak:


Pkt. 3 i 5 są zbędne skoro można zapisać plik do innego pliku, potem usunąć oryginalny i zmienić nazwę pliku. To chyba będzie szybsze niż zabawa ze strumieniem?

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: piątek, 28 września 2012, 20:21
przez polymorphism
No tak, w ostatnim zdaniu napisałem, że opcja z zapisywaniem od razu do pliku jest prostsza. A czy szybsza? Być może.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: sobota, 29 września 2012, 09:21
przez Cyfrowy Baron
Sprawdzałem, przeglądałem fora i nie da się tego obejść.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: sobota, 29 września 2012, 15:15
przez Mironas
Mi najbardziej pasuje zapisanie IMAGE do innego pliku, usunięcie IMAGE, usunięcie oryginalnego pliku i zmiana nazwy zapisanego pliku. Jedyne
dodatkowe czynności to kasowanie pliku i zmiana nazwy pliku. Nie trzeba tworzyć dodatkowych obiektów IMAGE/BITMAP ani strumieni, co przy dużych rozmiarach będzie pamięcio- i czasochłonne.

Przy małych, pojedynczych plikach można też zrobić podwójne RotateFlip obracając obraz np 2 razy w pionie. Prostsze do wykonania.

Dzięki za zaangażowanie i za wszystkie uwagi, sugestie i pomysły.

Re: Gdiplus::Image - Zapis do pliku

Nowy postNapisane: sobota, 29 września 2012, 15:25
przez Cyfrowy Baron
Mironas napisał(a):Przy małych, pojedynczych plikach można też zrobić podwójne RotateFlip obracając obraz np 2 razy w pionie. Prostsze do wykonania.


Tego nie polecam, gdyż obrazek ulegnie modyfikacji.

Sugerowałbym poszukanie innego sposobu na zapisywanie tagów do plików graficznych.