Jak načíst obrázek z disku?   zodpovězená otázka

VB.NET, Grafika

Zdravím všechny příznivce VB a rád bych se zeptal na takovou drobnost:

Potřebuji načíst obrázek z disku do proměnné (případně do pictureboxu)

Tady problém nění, pokud tedy používám standardní metodu

img=System.Drawing.Bitmap.FromFile(Path)

S takto načteným obrázkem provádím určité machinace a problém nastává ve chvíli, kdy chci upravený obrázek opět uložit do původního úložiště (souboru).

Vše totiž nasvědčuje tomu, že při otevření souboru pro načtení obrázku výše uvedenou metodou tento soubor zůstává otevřen a po načtení se sám neuzavře, přitom já neznám způsob, jak k tomuto souboru přistoupit, abych ho zavřel a umožnil tak provádět s ním další manipulace.

Jediné, co mne napadá, je načíst soubor přes nějaký standardní reader do streamu, soubor uzavřít a ze streamu vytvořit v mém SW zpětně obrázek, ale než se do toho pustím, chtěl jsem se zeptat, neexistuje-li nějaká jednodušší cesta.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Zkuste obrázek ze souboru uložit do jiné bitmapy a původní ukončit

  Dim img2 As Bitmap = img
  img.Dispose()

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Děkuji za snahu pomoci, ale tato cesta mi taktéž nefunguje.

Pokud si udělám jednoduchý příklad:

  Private img As Bitmap

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim im As Bitmap
        im = Bitmap.FromFile("c:\obr.jpg")
        img = im.Clone()
        im.Dispose()



    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        img.Save("c:\obr.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)

    End Sub

tak pokud v tom tlačítku 2 dám libovolný jiný název souboru, chodí to bez problému, pokud se ale snažím přepsat ten soubor původní, vyhodí to chybu.

Dokonce když ten obrázek načtu, nemohu s uvedeným souborem manipulovat ani třeba v průzkumníku, dokud program neukončím.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Zkuste to bez "clone", jen "img = im"

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

To vám nemůže fungovat z prostého důvodu: Vy totiž chcete přiřadit proměnnou REFERENČNÍHO typu do jiné proměnné referenčního typu, což znamená, že proměnná img se bude v paměti odkazovat na naprosto stejný objekt jako proměnná im, tudíž když uvolníte (Dispose) img, bude uvolněno i im.

Dalo se by použít následující řešení:

Dim path As String = "C:\WINDOWS\winnt256.bmp"
Dim imageStream As MemoryStream
Using imageReader As Stream = File.OpenRead(path)
  Dim buffer(My.Computer.FileSystem.GetFileInfo(path).Length - 1) As Byte
  imageReader.Read(buffer, 0, buffer.Length)
  imageStream = New MemoryStream(buffer)
End Using
Dim img As New Bitmap(imageStream)

Tímto kódem načtete celý obrázek do paměťového streamu, na základě kterého potom vytvoříte obrázek. S fyzickým souborem budete pracovat pouze po dobu jeho načtení z disku do paměti.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Děkuji za opravu, již jednou jsem tu podlehl podobnému bludu. Snad si to nyní budu pamatovat.

Napadla mě ještě možnost, berlička s pomocným souborem - překopírovat originální soubor do pomocného, ten zpracovat, výsledkem přepsat originální soubor a pomocný vymazat.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Zdravím, ale to by asi nebylo taky moc řešením, protože on se mi ten původní (otevíraný) soubor "odemykal" až po ukončení programu (evidentně gargabe na něj z vysoka kašle), a jelikož mám v plánu upravovat těch obrázků v jednom sezení i několik desítek, musel bych pro každý vytvářet samostatnou kopii a tyto bych mohl mazat až po ukončení programu, to ale už program nepracuje :-(, takže bych to musel vymazat na začátku příštího sezení, což by asi nebylo to nejlepší.

Jinak to "clone" jsem použil právě z důvodu, že jsem předpokládal, že image, resp. bitmap je referenční proměnná, takže skutečné "překopírování" do jiné proměnné je právě řešeno tím clone, ale stejně to nepomohlo.

Ale i tak Vám děkuji za snahu - zase jsme o něco chytřejší.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Zkusil jsem změnit kód v Button1 na:

    Dim im As Bitmap
    im = Bitmap.FromFile("c:\obr.jpg")
    im.Save("c:\pom.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
    img = Bitmap.FromFile("c:\pom.jpg")
    im.Dispose()

šlo by i s IO soubor do pomocného zkopírovat bez načítání, ale načtení jsem zvolil záměrně, zda půjde i tak přepsat

v Button2 jsem upravil kód na:

    img.Save("c:\obr.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
    img.Dispose()
    System.IO.File.Delete("c:\pom.jpg")

a prošlo to. Kontroloval jsem jen, že po kliku na Button1 se pom.jpg objevil, po Button2 zmizel a obr.jpg změnil datum.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Kupodivu máte pravdu - ale tomu teda skutečně nerozumím. Nejde mi teď o vyřešení problému (ten je vyřešen-funkčnost zajištěna), ale o o tázku, proč se to tak chová?

Proč to neuvolní soubor v případě, že "disposnu" proměnnou, kterou jsem před tím "naklonoval", ale uvolní to soubor v případě, když ho uložím. Přitom v dokumentaci o "clone" píší, že vytvoří exaktní kopii objektu, takže ta by snad už s tím původním neměla mít co do činění. Nebo se mýlím?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Metoda Dispose obecně slouží k uvolnění Unmanaged zdrojů. Takové zdroje mohou být právě třeba interní implementace Streamu (ve skutečnosti Handle na soubor). Zatím jsem to tedy dopodrobna nezkoumal, ale jsem přesvědčen že třída Bitmap je vnitřně reprezentována právě otevřeným Streamem namapovaným na daný obrázek a tím zavoláním Dispose se potom Stream uzavře.

Metoda Clone obecně slouží k vytvoření kopie objektu a je na autorovi třídy jak jí implementuje (může jí implementovat tak že to nebude 1:1 kopie ale reference na stejný objekt což se implementuje daleko jednodušeji).

Co se týče Garbage kolekce, tak ta se aplikuje pouze na objekty, na které UŽ NIKDE NEEXISTUJE REFERENCE. Pokud máte tedy proměnnou typu Bitmap na úrovni třídy (Form) a ne metody, pak je jasné že Garbage Collector se o toto nebude starat i přesto že jste použil metodu Dispose.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Děkuji, de facto mi popisujete chování tak, jak jsem si jej alespoň rámcově představoval. jenomže to mi právě nějak nesedí, přesněji veškeré mé teoretické znalosti (nebo alespoň představy o tom, že již něco vím) berou za své:

1) Bylo nám vysvětlováno, že proměnná deklarovaná na úrovni modulu má platnost pouze v rámci tohoto modulu, tedy v kódu

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim im As Bitmap
    im = Bitmap.FromFile("c:\obr.jpg")
    img = im.Clone()
    im.Dispose()
End Sub

je im deklarováno přesně dle výše uvedeného, to dispose jsem tam dal dle rady kolegy Rennera, ale žil jsem v domnění, že by tam být ani nemuselo, že stejný účel by mělo splnit i ukončení definičního oboru proměnné, tedy metody, ve které byla deklarována. Leč chyba lávky, nechová se to tak...

Evidentně je zakopán pes v té metodě Clone, protože pokud tam tato není a s im manipuluju pouze tak, že ji třeba uložím na disk pod novým názvem souboru (viz příklad, který pan Renner navrhl jako poslední), pak to vše fubguje OK a soubor se odemkne.

S tou libovůlí při implementaci metody Clone jste mne tak trochu zaskočil, protože kde pak člověk má zjistit, jak to vlastně funguje, když v manuálu metody je napsáno pouze, že "Creates an exact copy of this System.Drawing.Image".

A krom toho jsem doposud žil v bláhových představách, že přiřazení může být realizováno hodnotou (do zcela nezávislého nového objektu) nebo odkazem (ukazuje na stejný objekt).

No a pokud si metodu upravím:

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
   Dim im As Bitmap
   im = Bitmap.FromFile("c:\obr.jpg")
   img = im.Clone()
   im.Dispose()
   im = Nothing
 
   Me.PictureBox1.Image = img

End Sub

pak i když po vytvoření "klonu" proměnné im do proměnné img proměnnou im "disposnu" a pro jistotu ještě nastavím na nothing,

tak mi v proměnné img vše zůstane nedotčeno a zobrazí se mi do pictureboxu.

Ještě donedávna bych tedy byl přesvědčen, že se skutečně vytvořila kopie obrázku a ne odkaz na obrázek původní.

Přitom soubor na disku stále zůstává uzamčen jakékoliv manipulaci!?

Teď jsem ještě zkusil přidat do metody (místo toho zobrazení v pictureboxu)

img.dispose()

a okamžitě se mi soubor na disku uvolnil. Tam skutečně zůstává nějaká reference, ale proč (když objekt "bitmap" nemá se souborem ze kterého byl načten snad už nic společného, takže selským rozumem by se měl ten soubor "odpojit" samovolně okamžitě po načtení), to mi zůstává záhadou.

A ještě větší záhadou pro mne je, jak takové chování odhalit jinak, než "neprogramátorsky" metodou pokus-omyl?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Takže ještě jednou.

Jako vždy, tak i tentokrát jsem musel dojít k tomu, že chybu bylo nutno hledat mezi mou klávesnicí a židlí:

Jednak jsem na základě rozboru zde uvedeného nalezl přímou cestu, která je částečně funkční, ale vůbec té funkci nerozumím:

- pokud své metody změním na:

 Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click
   Using im As Bitmap = Bitmap.FromFile("c:\obr.jpg")
       img = im.Clone()
   End Using
   Me.PictureBox1.Image = img
end sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
   img.RotateFlip(RotateFlipType.Rotate90FlipNone)
   img.Save("c:\obr.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
End Sub

tak sice ukládání funguje takřka bez problému, ale:

- jakmile za pomocí button7 načtu obrázek, tak soubor na disku zůstává uzamčen proti jakékoliv manipulaci

- přitom ukládání (za pomocí tlačítla Button2 a jeho metody) funguje bez chyby (i na "uzamčeném" souboru!!) a navíc, po aplikaci této metody se soubor na disku odemkne (!!??!!) bez toho, že bych img (který je globální, tzn. stále platí) odstraňoval.

Tak tomu už vůbec nerozumím...

Průšvih je, pokud se mi podaří zmáčknout tlačítko Button7 2x po sobě, to totiž "zamkne soubor na disku natolik, že již nelze realizovat uložení tlačítkem Button2.

Ne, že by se jednalo o řešení, u kterého bych měl nějaký důvod je použít, ale na tomto příkladu zjišťuji, jak málo toho vím, že nejsem schopen pochopit ani takové základní souvislosti, proč se to chová, jak se to chová.

No ale ke konečnému řešení.

Mimo profesionální řešení pana Linharta za pomoci Streamu a funkčního řešení pana Rennera s pomocným souborem jsem nakonec došel k poznání, že je možno klidně použít i můj původní kód, pokud v něm ale opravím svou chybu spočívající ve skutečnosti, že jsem jaksi pozapomněl definovat img jako novou instanci třidy bitmap. takže správně by to mělo znít:

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
   Dim im As Bitmap
   im = Bitmap.FromFile("c:\obr.jpg")
   img = New Bitmap(im)
   im.Dispose()
   Me.PictureBox1.Image = img
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
   img.RotateFlip(RotateFlipType.Rotate90FlipNone)
   img.Save("c:\obr.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
End Sub

Tento kód je již funkční, ihned po načtení obrázku je soubor na disku uvolněn, takže jakékoliv ukládání nečiní pražádných problémů.

Děkuji všem, kteří mi k vyřešení tohoto problému dopomohli!

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Takže po menším průzkumu jsem zjistil, že funkce Clone třídy Image (od které je odvozen Bitmap) volá Win32 API funkci GdipCloneImage, která uzamyká soubor pro výhradní použití dokud není uvolněno příslušné Handle, které se mimojiné uvolňuje i zavoláním metody Save, což by přesně vysvětlovalo vámi popsané chování.

Jinak použijete-li Using...End Using tak metoda Dispose bude zavolána automaticky po provedení kódu uvnitř této klauzule (proto také Using jde použít pouze pro třídy implementující rozhraní IDisposable).

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Děkuji z Vaše vysvětlení i energii, kterou jste mému problému věnoval. Bohužel si z uvedené debaty odnáším vědomí, že tyto finesy jazyka jsou již hóóódně "vyšší dívčí" které překračují obzor mých vědomostí, proto pro mne bude existovat v takových případech jediná vývojová metoda "pokus - omyl".

Ještě že jsou mezi námi tak ochotní (a hlavně znalí) lidé jako jste Vy, takže zbývá naděje i v případech, kdy pokus-omyl selže.

Přeji krásný den.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Díííky! Toť přesně ono.

Sice jsem zkoušel jít podobnou cestou, ale nasekal jsem tam pár chyb - jednak jsem při stanovení velikosti toho bufferu zapomněl odečíst tu jedničku z rozměru pole, a pak jsem se trošku zamotal v těcch streamech (zatím jsem s nimi moc nepracoval, tak jsem tam omylem použil filestream).

Ještě jednou Vám děkuji!

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback