Joskus ennenkin ehkä kyselty. Kuvan lataus onnistuu LoadPicture-funktiolla, jossa annetaan kuvatiedoston nimi. Kyseessä on sovellus, jossa kuvia on satoja ja niistä näytetään muutama. Kuvat ovat kooltaan noin 10 kB eli aika pieniä. Kuvat ovat joko erillisinä jpg-tiedostoina kuvakansiossa tai kasattuna binääritiedostoon muootoon tiedostonimi ja itse kuvatiedosto merkkijonona. Nyt haluaisin saada kuvat Imageen suoraan tuosta binääritiedostosta ilman että ne tulisi ensin tallentaa erilliseen kuvatiedostoon. Onko mitenkään mahdollista?
Heippa setä!
joo mahdollista on ja aivan käsittämättömän iisiä... (.NET:ssä vielä iisimpää)
Dim fpath As String, fname As String Private Declare Function CreateStreamOnHGlobal _ Lib "ole32" (ByVal hGlobal As Long, ByVal _ fDeleteOnRelease As Long, ppstm As Any) As Long Private Declare Function GlobalAlloc Lib "kernel32" _ (ByVal uFlags As Long, ByVal dwBytes As Long) As Long Private Declare Function GlobalLock Lib _ "kernel32" (ByVal hMem As Long) As Long Private Declare Function GlobalUnlock Lib _ "kernel32" (ByVal hMem As Long) As Long Private Declare Function OleLoadPicture Lib "olepro32" _ (pStream As Any, ByVal lSize As Long, ByVal _ fRunmode As Long, riid As Any, ppvObj As Any) As Long Private Declare Sub CopyMemory Lib "kernel32.dll" Alias _ "RtlMoveMemory" (ByRef Destination As Any, _ ByRef Source As Any, ByVal Length As Long) Private Sub Form_Load() fpath = Environ("userprofile") & "\Työpöytä\" ChDir fpath Shell ("cmd /c copy /b *.jpg imgdata.dat"), vbHide End Sub Private Sub Command1_Click() fname = "imgdata.dat" If Dir(fpath & fname) = "" Then MsgBox "Kuvadataa ei löydy!" Exit Sub End If Open fpath & fname For Binary Access Read As #1 If LOF(1) < 100 Then Close #1 MsgBox "Kuvadata on vahingoittunut!" Exit Sub End If Dim fstr As String Dim delay As Single fstr = Space(LOF(1)) Get #1, , fstr: Close #1 Dim fileStrArray() As String fstr = Replace(fstr, "ÿØÿà", "SplITteRÿØÿà") '(.jpg kuvatiedoston 4 ensimmäistä merkkiä = "ÿØÿà") fileStrArray = Split(fstr, "SplITteR") fstr = "" For i = 1 To UBound(fileStrArray) back: On Error GoTo PicErrorHandler Dim imgData() As Byte imgData = StrConv(fileStrArray(i), vbFromUnicode) Set Image1.Picture = _ ArrayToPicture(imgData(), 0, UBound(imgData) + 1) delay = Timer + 5 Do While delay > Timer: DoEvents: Loop 'TAKAISIN Merkkijonoksi & Tiedostoksi 'fstr = StrConv(imgData, vbUnicode) 'fname = "imgdata" & Cstr(i) & ".jpg" 'Open fpath & fname For Binary Access Write As #1 'Put #1, , fstr: Close #1 Erase imgData Next i Exit Sub PicErrorHandler: Err.Clear If i < UBound(fileStrArray) Then i = i + 1: GoTo back End If End Sub Public Function ArrayToPicture(inArray() As Byte, _ Offset As Long, Size As Long) As IPicture Dim o_hMem As Long Dim o_lpMem As Long Dim aGUID(0 To 3) As Long Dim IIStream As IUnknown aGUID(0) = &H7BF80980 aGUID(1) = &H101ABF32 aGUID(2) = &HAA00BB8B aGUID(3) = &HAB0C3000 On Error GoTo FuncErrorHandler o_hMem = GlobalAlloc(&H2&, Size) If Not o_hMem = 0& Then o_lpMem = GlobalLock(o_hMem) If Not o_lpMem = 0& Then CopyMemory ByVal o_lpMem, inArray(Offset), Size Call GlobalUnlock(o_hMem) If CreateStreamOnHGlobal(o_hMem, 1&, IIStream) = 0& Then Call OleLoadPicture(ByVal ObjPtr(IIStream), 0&, _ 0&, aGUID(0), ArrayToPicture) End If End If End If Exit Function FuncErrorHandler: Err.Clear End Function
Kiitoksia Nea. Sinulta näköjään löytyy ratkaisu ongelmaan kuin ongelmaan. Tuossa kiinnostaa nyt funktio ArrayToPicture. Mistä ihmeestä tuon kehitit? Joitakin juttuja näyttää olevan, mitä ei VB5 tue mutta kylläkin VB6. Ne voi ilmeisesti kyllä kiertää. Tarvitaanko välttämättä tuota parametria vbFromUnicode. Mulla olis periaatteessa valmiina tuo kuvatiedoston data merkkijonona, onko se tuo imgData? Saan sen siis lukemalla binääritiedostosta tietyn pätkän, jonka pituus on kuvatiedoston koko tavuina.
Jos lataat suoraan byte array:n täyteen tarvittavaa dataa, eli et kierrätä turhaan stringin kautta, niin sitten tuota StrConvia ei tarvita. Tuo ArrayToPicture pystyy ottamaan ihan osadataa, kunhan vain offset (eli kuvadatan alkukohta) ja pituus on oikein.
ArrayToPicture taas luo OLE-streamin, joka täytetään ja sitten ladataan kuva täytetystä streamista.
Testasin tuon koodin pienin muutoksin korvaamalla replacen ja Splitin muilla säädöillä ja sain toimimaan VB5:llä. Ilmeisesti tuo Byte array:n käyttö on näppärämpi ja lienee nopeampikin. Testasin ydellä kuvatiedostolla eikä sieltä löytynyt merkkijonoa "ÿØÿà" kuin alusta eli tuota Split-operaatiota ei tarvinnut lainkaan. Kuinkas tuo kuvadata ladataan suoraan byte arrayhin. Saako sen Get-käskyllä suoraan byte arrayhin stringin sijaan.
Edit: Sain kokeilemalla toimimaan. Suurkiitokset vihjeistä!
Jep, vahvistetaan se, että on tosiaan (paljon) nopeampaa ladata suoraan byte arrayn kautta :) StrConvit ja Splitit on aikamoisen hitaita operaatioita verrattuna siihen, että suoraan vetää dataa sellaisenaan läpi.
Kun jätit mainitsematta, niin todetaan mahdollisille muille lukijoille että byte array:n voi tosiaan suoraan Getillä täyttää.
Heippa taas setä!
jos yhtään tarkemmin perehdyit koodiin niin...sinulle taisi selvitä, että esimerkkiohjelman form_load tapahtumassa pumpataan kaikki aktiivisen hakemiston .jpg filut yhteen .dat tiedostoon, jolloin on mielestäni helpointa erotella kuvat stringistä split-funktion avulla jne... (ja muillekin lukijoille - joillain tuntuu olevan asiaa aina vähän niinkuin jälkikäteen..) no joo...
Elikä jos VB5:ssa ei vbFromUnicode-konversio toimi edellisen esimerkin tavalla niin homma hoituu esim. vaikkapa seuraavasti
'vaihda tähän...
Private Sub Command1_Click() fname = "imgdata.dat" If Dir(fpath & fname) = "" Then MsgBox "Kuvadataa ei löydy!" Exit Sub End If Open fpath & fname For Binary Access Read As #1 If LOF(1) < 100 Then Close #1 MsgBox "Kuvadata on vahingoittunut!" Exit Sub End If Dim fstr As String Dim delay As Single fstr = Space(LOF(1)) Get #1, , fstr: Close #1 Dim fileStrArray() As String 'eli siis lisätään stringiin jokaisen tiedosto- 'pätkän eteen merkkijono "SplITteR" .... fstr = Replace(fstr, "ÿØÿà", "SplITteRÿØÿà") '... jolloin erotin "SplITteR" haihtuu splitatessa 'kukin stringin sisältämä tiedosto taulukkoon... fileStrArray = Split(fstr, "SplITteR") fstr = vbNullString For i = 0 To UBound(fileStrArray) back: On Error GoTo PicErrorHandler 'tämä hidastaa, mutta sehän ei ole minun ongelmani... ReDim imgData(0) As Byte For j = 0 To Len(fileStrArray(i)) - 1 Dim tmpLng As Long 'pistetään filu-Stringin merkit longiksi tmpLng = CLng(Asc(Mid(fileStrArray(i), j + 1, 1))) 'ja muutetaan longit funktiossa tavuiksi, jolloin 'matkalla ei pääse tapahtumaan ylivuotoja... imgData(j) = LongToByte(tmpLng) If j < Len(fileStrArray(i)) - 1 Then _ ReDim Preserve imgData(UBound(imgData) + 1) Next j 'jne... Set Image1.Picture = _ ArrayToPicture(imgData(), 0, UBound(imgData) + 1) 'delay siksi että jaksoin katsella kutakin 'kuvaa aina 5 sekkaa kerralla... delay = Timer + 5 Do While delay > Timer: DoEvents: Loop Erase imgData Next i Erase fileStrArray Exit Sub PicErrorHandler: Err.Clear If i < UBound(fileStrArray) Then i = i + 1: GoTo back End If End Sub
...ja lisää tämä
Public Function LongToByte(ByVal lng As Long) As Byte Dim o_Byte As Byte CopyMemory o_Byte, ByVal VarPtr(lng), Len(lng) LongToByte = o_Byte End Function
Replace ja Split eivät toimi VB5:ssä. StrConv kyllä toimi mutta ei tarvi kun lukee datan suotaan arrayhin.
setä kirjoitti:
Replace ja Split eivät toimi VB5:ssä. StrConv kyllä toimi mutta ei tarvi kun lukee datan suotaan arrayhin.
Kiitos vinkeistä, mutta joudun erottelemaan datan tunnusluvun mukaan, jolla kuva liitetään tiettyyn henkilöön. Binääritiedostossa on tuo tunnus, kuvadatan pituus tavuina ja kuvadata. Näitä muutama sata. Testailen miten näppärimmin käy datan luku arrayhin.
Tuo ArrayToPicture on jopa hieman nopeampi kuin LoadPicture-funktio. Suurin hyöty on kuitenkin kun kaikki yli 600 kuvatiedostoa voi niputtaa yhteen binääritiedostoon ja lukea suoraan sieltä. Jälleen kerran sain täältä ratkaisun ongelmaani ja melkoisen nopeasti.
Heippa taas setä!
tässä vielä toimiva Split-funktio jos vaikka ilmaantuis joskus tarvetta...
'Generaaleihin tai Public'si moduuliin Private aputaulu() As String
käyttö:
Dim taulu() As string taulu = Splittaa(merkkijono$, erotin$)
Public Function Splittaa(ByVal Merkkijono As _ String, ByVal erotin As String, Optional ByVal ok As Boolean, _ Optional ByVal apuT As Variant) As Variant If Not ok Then ReDim Preserve aputaulu(0) As String Else aputaulu = apuT End If Dim sijainti As Long, i As Long sijainti = InStr(1, Merkkijono, erotin, vbBinaryCompare) If Left(Merkkijono, Len(erotin)) = erotin Then Merkkijono = Right(Merkkijono, _ Len(Merkkijono) - Len(erotin)) Splittaa Merkkijono, erotin, True, aputaulu ElseIf sijainti > 0 And _ Left(Merkkijono, Len(erotin)) <> erotin Then aputaulu(UBound(aputaulu)) = _ Left(Merkkijono, sijainti - 1) Merkkijono = _ Right(Merkkijono, Len(Merkkijono) _ - Len(aputaulu(UBound(aputaulu)))) ReDim Preserve aputaulu(UBound(aputaulu) + 1) Splittaa Merkkijono, erotin, True, aputaulu ElseIf sijainti = 0 And _ Len(Merkkijono) > 0 Then aputaulu(UBound(aputaulu)) = Merkkijono End If Splittaa = aputaulu() End Function
Aihe on jo aika vanha, joten et voi enää vastata siihen.