Kirjautuminen

Haku

Tehtävät

Koodit: VB.NET: Linssiefekti

Kirjoittaja: peki

Kirjoitettu: 28.05.2004 – 28.05.2004

Tagit: koodi näytille, vinkki

Linssi efekti. Perustuu aiempaan vesi efektiini.
Luodaan korkeuskenttä(Height Field) ja tehdään rendataan kuva sen "läpi".
Vaatii kuvan "koe.jpg" C:\ aseman juureen.
Koodin kääntäminen onnistuu edelleen vain .net 2003:lla johtuen renderöimisen "raytracing" osuudessa käytetystä >> operaattorista.

Exe löytyy: http://koti.mbnet.fi/peku1/Lens.exe
Älä aja ohjelmaa suoraan IEExec ohjelman läpi, vaan tallenna se kiintolevylle, muuten c: asemalla sijaitsevaa kuvaa ei voida avata. (Itselläni kävi näin)

Mietin tuota omaa vesi koodiani hieman lisää, ja tulin siihen tulokseen, että korkeuskartan läpi renderöintiä voi läyttää kaikkeen kivaan. Voit tehdä millaisen tahansa korkeuskartan ja renderöidä sen läpi.

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Public Class frmLens
    Inherits System.Windows.Forms.Form

    ' yleisiä muuttujia
    Private bmp As Bitmap
    Private waves As Long(,,)
    Private bmpWidth As Integer
    Private bmpHeight As Integer
    Private activeBuffer As Integer = 0
    Private weHaveWaves As Boolean
    Private bmpBytes As Byte()
    Private bmpBitmapData As BitmapData

    ' asettaa linssin säteen
    Private LensRadius As Integer = 35

    Dim fps As Long

    Dim fnt As New Font("Arial", 20)

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Haluamme piirtää itse -> ei automaattipäivitystä
        SetStyle(ControlStyles.UserPaint, True)
        'Piirto tapahtuu vain ja ainoastaan paint metodissa
        SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        'Haluamme käyttää myös kaksoispuskurointia
        SetStyle(ControlStyles.DoubleBuffer, True)

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.
    'Do not modify it using the code editor.
    Friend WithEvents EffectTimer As System.Windows.Forms.Timer
    Friend WithEvents picSurface As System.Windows.Forms.PictureBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.components = New System.ComponentModel.Container
        Me.EffectTimer = New System.Windows.Forms.Timer(Me.components)
        Me.picSurface = New System.Windows.Forms.PictureBox
        Me.SuspendLayout()
        '
        'EffectTimer
        '
        Me.EffectTimer.Enabled = True
        Me.EffectTimer.Interval = 1
        '
        'picSurface
        '
        Me.picSurface.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
                    Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.picSurface.Location = New System.Drawing.Point(0, 0)
        Me.picSurface.Name = "picSurface"
        Me.picSurface.Size = New System.Drawing.Size(448, 368)
        Me.picSurface.TabIndex = 0
        Me.picSurface.TabStop = False
        '
        'frmLens
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(448, 366)
        Me.Controls.Add(Me.picSurface)
        Me.Name = "frmLens"
        Me.Text = "Lens"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private Sub frmWater_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Ladataan bittikartta
        bmp = New Bitmap("C:/koe.jpg")
        ' otetaan ylös arvot(lukeminen suoraan muuttujasta nopeampaa)
        bmpHeight = bmp.Height
        bmpWidth = bmp.Width
        ' alustetaan aaltotaulukko
        ReDim Me.waves(bmpWidth, bmpHeight, 2)
        ' hankitaan bittikartta dataa(NOPEA TAPA!!!)
        ReDim Me.bmpBytes(bmpWidth * bmpHeight * 4)
        bmpBitmapData = bmp.LockBits(New Rectangle(0, 0, bmpWidth, bmpHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
        Marshal.Copy(bmpBitmapData.Scan0, bmpBytes, 0, bmpWidth * bmpHeight * 4)

        PutLens(Int(Rnd() * bmpWidth), Int(Rnd() * bmpHeight), Int(Rnd() * 100) + 100)
    End Sub

    Private Sub picSurface_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picSurface.Paint
        Dim tmp As Bitmap = bmp.Clone()
        Dim x, y As Integer
        Dim xOffset, yOffset As Integer
        Dim alpha As Integer
        If (weHaveWaves) Then
            Dim tmpData As BitmapData = tmp.LockBits(New Rectangle(0, 0, bmpWidth, bmpHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
            Dim tmpBytes(bmpWidth * bmpHeight * 4) As Byte
            Marshal.Copy(tmpData.Scan0, tmpBytes, 0, bmpWidth * bmpHeight * 4)

            x = 1
            Do While (x < (bmpWidth - 1))
                y = 1
                Do While (y < (bmpHeight - 1))
                    'Tämä on johdettu raytracingistä(AH!) heijastetaan oikea pikseli siten,
                    'että saadaan aikaan valoa taittava efekti.
                    xOffset = waves(x - 1, y, activeBuffer) - waves(x + 1, y, activeBuffer) >> 3
                    yOffset = waves(x, y - 1, activeBuffer) - waves(x, y + 1, activeBuffer) >> 3
                    If ((Not (xOffset = 0)) Or (Not (yOffset = 0))) Then
                        'tsekkaa törmailyt(heijastetuille pikseleille)
                        If (x + xOffset >= bmpWidth - 1) Then xOffset = bmpWidth - x - 1
                        If (x + xOffset < 0) Then xOffset = -x

                        If (y + yOffset >= bmpHeight - 1) Then yOffset = bmpHeight - y - 1
                        If (y + yOffset < 0) Then yOffset = -y
                        'luodaan alpha(tarvitaan vain, jos kuvassa on kohtia, joissa on alpha arvoja[läpinäkyvyyttä])
                        alpha = CInt(200 - xOffset)
                        If (alpha < 0) Then
                            alpha = 0
                        ElseIf (alpha > 255) Then
                            alpha = 254
                        End If
                        'asetetaan värit oikeisiin kohtiin(napataan oikeasta kohdasta bittitaulukkoa)
                        tmpBytes(4 * (x + y * bmpWidth)) = bmpBytes(4 * (x + xOffset + (y + yOffset) * bmpWidth))
                        tmpBytes(4 * (x + y * bmpWidth) + 1) = bmpBytes(4 * (x + xOffset + (y + yOffset) * bmpWidth) + 1)
                        tmpBytes(4 * (x + y * bmpWidth) + 2) = bmpBytes(4 * (x + xOffset + (y + yOffset) * bmpWidth) + 2)
                        tmpBytes(4 * (x + y * bmpWidth) + 3) = alpha
                    End If
                    y += 1
                Loop 'y
                x += 1
            Loop 'x
            'kopioidaan data takasin
            Marshal.Copy(tmpBytes, 0, tmpData.Scan0, bmpWidth * bmpHeight * 4)
            tmp.UnlockBits(tmpData)
        End If
        e.Graphics.DrawImage(tmp, 0, 0, picSurface.ClientRectangle.Width, picSurface.ClientRectangle.Height)
        e.Graphics.DrawString("fps: " & fps, fnt, Brushes.Black, 50, 50)
    End Sub

    Private Sub PutLens(ByVal x As Integer, ByVal y As Integer, ByVal height As Short)
        ' Simuloi pyöreää linssiä
        ' täyttää aaltotaulukon sopivilla arvoilla
        ' Käytetään hyväksi kosiniaaltoa
        ' (Jos tykkäät geomatriasta/algebrasta RAKASTAT tätä aliohjelmaa
        ' Silloin tulet nauttimaan myös renderöinnin "raytracing" osuudesta)
        ' Perustuu samaan kaavaan, kuin vesiefekti

        ' Nyt meillä ON aaltoja :D
        weHaveWaves = True
        Dim radius As Integer = LensRadius
        'etäisyyden neliö
        Dim distSquared As Double
        'aloitetaan "pallon" reunoilta
        Dim i As Integer = -radius
        Dim tmpX, tmpY As Integer
        Array.Clear(waves, 0, waves.Length)

        Do While (i <= radius)
            Dim j As Integer = -radius
            Do While (j <= radius)
                tmpX = x + i
                tmpY = y + j
                If (((tmpX >= 0) And (tmpX < bmpWidth - 1)) And ((tmpY >= 0) And (tmpY < bmpHeight - 1))) Then
                    ' i:n ja j:n välisen etäisyyden neliö
                    distSquared = Math.Sqrt(i * i + j * j)
                    ' jos etäisyys on < säde silloin linssi on "oikein"
                    If (distSquared < radius) Then
                        ' pistetään kosiniaaltoa silmukan osoittamaan paikkaan
                        waves(x + i, y + j, activeBuffer) = CShort(Math.Cos(distSquared * Math.PI / radius) * -height)
                    End If
                End If
                j += 1
            Loop 'j
            i += 1
        Loop 'i
    End Sub

    Private Sub picSurface_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picSurface.MouseMove
        ' Koska bittikarttaa skaalataan, täytyy hiiren koordinaatteja hieman säätää...
        Dim realX As Integer = CInt((e.X / CDbl(picSurface.ClientRectangle.Width)) * bmpWidth)
        Dim realY As Integer = CInt((e.Y / CDbl(picSurface.ClientRectangle.Height)) * bmpHeight)
        PutLens(realX, realY, 350)
    End Sub

    Private Sub frmWater_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Activated
        Do
            Dim d As Long = Now().Millisecond
            Dim ft As Long
            picSurface.Invalidate()
            Application.DoEvents()
            ft = Now().Millisecond - d
            If ft <> 0 Then fps = 1 / ft * 1000
        Loop
    End Sub

    Private Sub frmWater_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
        End
    End Sub
End Class

Kommentit

Bill Keltanen [29.05.2004 07:34:11]

#

Ai että oot peki hyvä tekee näitä :P

sooda [29.05.2004 10:41:02]

#

Eh??? Peki on ihan liian pro tollaiseksi otukseksi! :D

setä [29.05.2004 11:51:02]

#

Hei, ton exen lataus ei onnistu !

peki [29.05.2004 11:56:19]

#

hmm.
Itselläni kyllä onnistuu.
Mitä selainta käytät(itsellä Opera)
Jos käytät IE:tä paina linkkiä oikealla -> ja tallenna nimellä.
Älä siis anna IEExecin avata sitä, vaan tallenna se kiintolevylle.

setä [29.05.2004 11:58:04]

#

IE6.0. Vesi latautui ongelmitta mutta tämä ei.
erona oli kyllä se,ettei FrameWork ollut asennettuna kun imuroin tuon vesi.exen. Kun yritän imuroida Linssiä, tulee tuo FrameWork sotkemaan jotain.

peki [29.05.2004 12:01:34]

#

joo. itsellä käy samoin.
paina linkkiä oikealla hiirenkorvalla -> ja paina sitten: tallenna nimellä.

setä [29.05.2004 12:03:51]

#

Jeps, noin se onnistui. Tuli tolla vesi.exellä sama ongelma.
Joo, tosi hienosti pelaa. Kuvaa voi näköjään zoomata ja pienessä koossa nopeus kasvaa. fps näyttää 50 tai 100.

Meitzi [29.05.2004 12:48:20]

#

Tämä näytti sopivalta kokeilulta tehdä vähä optimointia.

Option Strict Off 'Ei suositeltava

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Public Class frmLens
    Inherits System.Windows.Forms.Form

    Const LensRadius As Integer = 35 'Linssin koko
    Const LensHeight As Integer = 350 'Linssi syvyys
    Const wavesWidth As Integer = LensRadius * 3 'Varaa linssille riittävän ison tilan
    Const wavesHeight As Integer = LensRadius * 3 'Varaa linssille riittävän ison tilan
    Const filename As String = "koe.jpg" 'Tiedostoa luetaan samasta hakemistosta kuin EXE

    ' yleisiä muuttujia
    Private bmp As Bitmap
    Private waves(wavesWidth, wavesHeight) As Long 'Miksi tämä oli kolmiulotteinen?
    Private bmpWidth As Integer
    Private bmpHeight As Integer
    'Private weHaveWaves As Boolean 'Turha
    Private bmpBytes As Byte()
    Private bmpBitmapData As BitmapData
    Private lensX As Integer 'Linssin paikka bittikartassa
    Private lensY As Integer

    Dim fps As Long

    Dim fnt As New Font("Arial", 20)

#Region " Windows Form Designer generated code "
'Jätin tämän pois koska se on sama (paitsi että poistin turhan Timerin)
#End Region

    Private Sub frmWater_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        If Not IO.File.Exists(filename) Then
            MsgBox("Filename " & filename & " does not exist.", MsgBoxStyle.Critical, "Serious error")
            End
        End If

        ' Ladataan bittikartta
        bmp = New Bitmap(filename)
        ' otetaan ylös arvot(lukeminen suoraan muuttujasta nopeampaa)
        bmpHeight = bmp.Height
        bmpWidth = bmp.Width

        ' hankitaan bittikartta dataa(NOPEA TAPA!!!)
        ReDim Me.bmpBytes(bmpWidth * bmpHeight * 4)
        bmpBitmapData = bmp.LockBits(New Rectangle(0, 0, bmpWidth, bmpHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
        Marshal.Copy(bmpBitmapData.Scan0, bmpBytes, 0, bmpWidth * bmpHeight * 4)

        CreateLens() 'Tehdään linssi, tämä tarvitsee tehdä vain kerran.
    End Sub

    Private Sub picSurface_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picSurface.Paint
        Dim tmp As Bitmap = bmp.Clone() 'Conversion virhe
        Dim x, y As Integer
        Dim xOffset, yOffset As Long
        Dim alpha As Integer

        Dim tmpData As BitmapData = tmp.LockBits(New Rectangle(0, 0, bmpWidth, bmpHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
        Dim tmpBytes(bmpWidth * bmpHeight * 4) As Byte
        Marshal.Copy(tmpData.Scan0, tmpBytes, 0, bmpWidth * bmpHeight * 4)

        For x = 1 To wavesWidth - 1
            For y = 1 To wavesHeight - 1
                'Tämä on johdettu raytracingistä(AH!) heijastetaan oikea pikseli siten,
                'että saadaan aikaan valoa taittava efekti.
                xOffset = waves(x - 1, y) - waves(x + 1, y) >> 3
                yOffset = waves(x, y - 1) - waves(x, y + 1) >> 3
                If ((Not (xOffset = 0)) Or (Not (yOffset = 0))) Then
                    If x + lensX > 0 And y + lensY > 0 And x + lensX < bmpWidth And y + lensY < bmpHeight Then
                        'Tarkista että pixeleit ei oteta reunojen ulkopuolelta
                        If (x + lensX + xOffset >= bmpWidth - 1) Then xOffset = 0
                        If (x + lensX + xOffset < 0) Then xOffset = 0

                        If (y + lensY + yOffset >= bmpHeight - 1) Then yOffset = 0
                        If (y + lensY + yOffset < 0) Then yOffset = 0
                        'luodaan alpha(tarvitaan vain, jos kuvassa on kohtia, joissa on alpha arvoja[läpinäkyvyyttä])
                        alpha = CInt(200 - xOffset)
                        If (alpha < 0) Then
                            alpha = 0
                        ElseIf (alpha > 255) Then
                            alpha = 254
                        End If
                        'asetetaan värit oikeisiin kohtiin(napataan oikeasta kohdasta bittitaulukkoa)
                        tmpBytes(4 * (x + lensX + ((y + lensY) * bmpWidth))) = bmpBytes(CInt(4 * (x + lensX + xOffset + ((y + lensY) + yOffset) * bmpWidth)))
                        tmpBytes(4 * (x + lensX + ((y + lensY) * bmpWidth)) + 1) = bmpBytes(CInt(4 * (x + lensX + xOffset + ((y + lensY) + yOffset) * bmpWidth) + 1))
                        tmpBytes(4 * (x + lensX + ((y + lensY) * bmpWidth)) + 2) = bmpBytes(CInt(4 * (x + lensX + xOffset + ((y + lensY) + yOffset) * bmpWidth) + 2))
                        tmpBytes(4 * (x + lensX + ((y + lensY) * bmpWidth)) + 3) = CByte(alpha)
                    End If
                End If
            Next y
        Next x
        'kopioidaan data takasin
        Marshal.Copy(tmpBytes, 0, tmpData.Scan0, bmpWidth * bmpHeight * 4)
        tmp.UnlockBits(tmpData)

        e.Graphics.DrawImage(tmp, 0, 0, picSurface.ClientRectangle.Width, picSurface.ClientRectangle.Height)
        e.Graphics.DrawString("fps: " & fps, fnt, Brushes.Black, 50, 50)
    End Sub

    Private Sub CreateLens()
        ' Simuloi pyöreää linssiä
        ' täyttää aaltotaulukon sopivilla arvoilla
        ' Käytetään hyväksi kosiniaaltoa
        ' (Jos tykkäät geomatriasta/algebrasta RAKASTAT tätä aliohjelmaa
        ' Silloin tulet nauttimaan myös renderöinnin "raytracing" osuudesta)
        ' Perustuu samaan kaavaan, kuin vesiefekti
        Dim radius As Integer = LensRadius
        'etäisyyden neliö
        Dim distSquared As Double
        'aloitetaan "pallon" reunoilta
        Dim i As Integer
        Dim x, y, j As Integer
        Dim tmpX, tmpY As Integer
        Array.Clear(waves, 0, waves.Length)

        x = CInt(wavesWidth / 2)
        y = CInt(wavesHeight / 2)

        For i = -radius To radius
            For j = -radius To radius
                tmpX = x + i
                tmpY = y + j
                If (((tmpX >= 0) And (tmpX < wavesWidth - 1)) And ((tmpY >= 0) And (tmpY < wavesHeight - 1))) Then
                    ' i:n ja j:n välisen etäisyyden neliö
                    distSquared = Math.Sqrt(i * i + j * j)
                    ' jos etäisyys on < säde silloin linssi on "oikein"
                    If (distSquared < radius) Then
                        ' pistetään kosiniaaltoa silmukan osoittamaan paikkaan
                        waves(x + i, y + j) = CShort(Math.Cos(distSquared * Math.PI / radius) * -LensHeight)
                    End If
                End If
            Next j
        Next i
    End Sub

    Private Sub PutMouse()
        lensX = CInt(picSurface.Cursor.Position.X - picSurface.PointToScreen(New Point).X - (wavesWidth / 3)) 'Kaiken järjen mukaan /2 pitäisi antaa oikea kohta, en jaksanut selvittää missä vika on
        lensY = CInt(picSurface.Cursor.Position.Y - picSurface.PointToScreen(New Point).Y - (wavesHeight / 3))

        ' Koska bittikarttaa skaalataan, täytyy hiiren koordinaatteja hieman säätää...
        lensX = CInt((lensX / CDbl(picSurface.ClientRectangle.Width)) * bmpWidth)
        lensY = CInt((lensY / CDbl(picSurface.ClientRectangle.Height)) * bmpHeight)
    End Sub

    Private Sub frmWater_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Activated
        Dim Sekunnit As Long
        Dim OldTime As DateTime
        Dim NewTime As DateTime
        Dim Frames As Long 'Lasketaan montako kertaa ruutu on piirretty

        Do
            PutMouse()

            picSurface.Invalidate()
            Application.DoEvents()
            'Lasketaan FPS vain kerran sekunnissa jolloin saadaan järkeviä arvoja
            If Now().Second <> Sekunnit Then
                OldTime = NewTime
                NewTime = Now 'GetTickCount
                fps = CLng(Frames / ((NewTime.Ticks - OldTime.Ticks) / 10000000))
                Frames = 0
                Sekunnit = Now().Second
            End If
            Frames += 1
        Loop
    End Sub

    Private Sub frmWater_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
        End
    End Sub
End Class

Käännetty EXE: http://koti.mbnet.fi/meitzi/Lens1.1.zip

setä [29.05.2004 13:44:48]

#

Huomattava parannus nopeudessa, hienoa !

miiro [30.05.2004 16:13:09]

#

Saisko tän/veden vb6selle

mamaze [30.05.2004 17:29:22]

#

miten ihmees toi valmis exe versio tilttas tosi perusteellisesti?

peki [30.05.2004 19:22:05]

#

kumpaa exeä käytit? Meitsin vai minun?
Omani saattaa kaatua alkeellisen fps countterin takia, mutta tiltata sen ei pitäisi. Outoa.

BlueByte [01.06.2004 14:28:44]

#

hyvä linssiefekti kun tuli punanen rasti ruutuun

monosyllabic [01.06.2004 16:00:38]

#

"Sovelluksen alustus epäonnistui (0x0000135). Lopeta sovellus valitsemalla OK."

,eli en tiedä VB:stä mitään ,mutta sen tiedän ,että tuo EXE ei toimi :D

peki [01.06.2004 20:39:45]

#

lainaus:

joo. itsellä käy samoin.
paina linkkiä oikealla hiirenkorvalla -> ja paina sitten: tallenna nimellä.

Tästä olen jo kertonut ennenkin, tuo johtuu siitä, että IEExec toimii kuin java appletti tulkki.
Sinun pitää siis ladata ohjelma koneellesi ja ajaa se sieltä käsin.

Todennäköisesti tuo errorisi johtuu siitä, että fps countterini on alkeellinen. Suosittelen kokeilemaan meitzin exeä. sen fps countteri on parempi ja koodi optimoitu tehokkaammin.
Kiitos Meitzille! Opin itsekin muutaman kikan.

Meitzi [01.06.2004 21:00:56]

#

.NET sovellusten suorittaminen suoraan IE:stä ei toimi tässä tapauksessa, koska silloin sovellus suoritetaan suojatussa "hiekkalaatikko" tilassa jolloin se ei pysty avamaan kovalevyltä mitään.

peki: Nojuu näytti vain ihan hienolta efektiltä kun kokeilin mutta oli vaan niin turkasen hias ;)

Huomasin melkein heti että koodissa oli aika helposti tehtävissä nopeampi johtuen vanhasta "aalto" pohjasta. (mm. minun koodi laskee vain linssin alueen, ei koko ruutua jne)

Fisher [27.07.2004 17:33:28]

#

saisko qb:lle tai turbo pascal 5:selle?

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta