• Welcome to Valhalla Legends Archive.
 

MNG add banner support

Started by Ringo, March 22, 2008, 03:11 AM

Previous topic - Next topic

Ringo

Ok, so, I got really bored and thought I would give loading MNG files ago, after looking at a few PNG/MNG files in hex.
There is TONS of things im not sure about (thats an under-statement) but it does just about work!
A few images have slightly messed up color for some reassion, and some of the multi framed images need abit more figgering out, as they dont always line up correctly.
Im pretty sure that multiple frames can/should be bound together to create 1 frame tho, but havent really looked into it that much.
Also, this requires transparent drawing, which I didnt bother with, so that doesnt help :P
I basicly built it around as many BNCS MNG's I could get my hands on, so it only supports 16 and 256 colors images and probly wont load other/non-W3 MNG's.
I also dunno where the frame delay is, so in my bot I just toggle the frames every 2 seconds.
Below, is my testing code (with the debug crap removed) and its not very error checky, and boy is it a mess!
Your welcome to use this code in anyway you see fit, it should be a good start/example of how to load MNG's!
If anyone would like to add to this, feel free, but I persionaly have lost interest and am now working on some other stuff.
It might be worth trying to find some documentation on MNG's, as I didnt bother and just worked from hex dumps mostly. (I got the PNG header from google tho)
If the quality of the way SC/D2 displays SMK stuff is to go by, this will work abit better than that, hence why I lost interest :)

Anyway, Injoy!

Some other notes:
The LoadMNG() function will return a BITMAPSTRUT array, which contains the handle to the image in memory, so you can then use a few GDI API's to draw etc.
Just remember to DeleteObject() on that handle when your finished/load a fresh, otherwise you will be eating up ram!
If your add banner is resizeable (like mine was when testing) you will need to do some math with the defalt animation width/height in the MNGDATAHEADER to scale everything correctly.
If you use somthing like transparentBitblt() and only clear the image on the 1st bitmap, that should massive improove the quality of multi frame animation (but will be limited to win2000 and later)
Other wise you will black out the last image and it wont look right (like i was doing in testing)
That said, only 1 multi framed mng was offseted strangely, so it might also be advised to wrap around the frames if part of them go over the orginal frame dimentions!

EDIT: ok, just glanced over the code after posting, and fixed the messed up colors. Turned out when i was shifting the pallet up for the bit map header, I was only shifting 768 bytes and not 1024 :p (woopsy)
So, color is perfect now!
Also, drawing with transparency should make the multi frame animations perfect as well, its only that 1 mng that was offsetted badly, but after seeing it on the client, it was offsetted badly there to!


Private Type RGBQUAD
    rgbBlue         As Byte
    rgbGreen        As Byte
    rgbRed          As Byte
    rgbReserved     As Byte
End Type
Private Type RGBTriple
    rgbBlue         As Byte
    rgbGreen        As Byte
    rgbRed          As Byte
End Type
Private Type BITMAPINFOHEADER
    biSize          As Long
    biWidth         As Long
    biHeight        As Long
    biPlanes        As Integer
    biBitCount      As Integer
    biCompression   As Long
    biSizeImage     As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed       As Long
    biClrImportant  As Long
End Type


Private Type MNGDATAHEADER
    FrameWidth      As Long
    FrameHeight     As Long
    Unknown0        As Long
    Unknown1        As Long
    Unknown2        As Long
    Unknown3        As Long
    Unknown4        As Long
    Unknown5        As Long
    Unknown6        As Long
End Type
Private Type PNGINFOHEADER 'IHDR
    Width           As Long
    Height          As Long
    Bits            As Byte
    ColorType       As Byte
    Compresion      As Byte
    Filter          As Byte
    Interlace       As Byte
    Unknown1        As Long
    Unknown2        As Long
End Type

Private Declare Function uncompress Lib "zlib.dll" (dest As Any, destLen As Any, _
    src As Any, ByVal srcLen As Long) As Long

Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateDIBitmap Lib "gdi32" (ByVal hdc As Long, _
    lpInfoHeader As Any, ByVal dwUsage As Long, lpInitBits As Any, _
    lpInitInfo As Any, ByVal wUsage As Long) As Long

Public Type BITMAPSTRUT
    Width  As Long
    Height As Long
    Handle As Long
    OffX   As Long
    OffY   As Long
End Type


Public Function LoadMNG(ByVal strFilePath As String, BitMap() As BITMAPSTRUT) As Boolean
    Dim FF          As Integer
    Dim S           As String
    Dim lngPos      As Long
    Dim bPal()      As Byte
    Dim bData()     As Byte
    Dim bOut()      As Byte
    Dim pIH         As PNGINFOHEADER
    Dim pBM         As BITMAPINFOHEADER
    Dim mDH         As MNGDATAHEADER
    Dim lngTest     As Long 'check headers
    Dim BitMapCount As Long
    Dim lngBackCol  As Long
    Dim bBackCol    As Boolean
   
    BitMapCount = -1
    lngPos = 1
   
    If Dir(strFilePath) = vbNullString Then Exit Function
   
    FF = FreeFile()
    Open strFilePath For Binary As #FF
        S = String(LOF(FF), 0)
        Get #FF, 1, S
    Close #FF
   
    Call CopyMemory(lngTest, ByVal S, 4)
    lngPos = lngPos + 12
    If Not lngTest = &H474E4D8A Then '.MNG
        Debug.Print "MNG Bad File Header ID"
        Exit Function
    End If
   
LoadMNGNewHeader:
    Call CopyMemory(lngTest, ByVal Mid$(S, lngPos, 4), 4)
    lngPos = lngPos + 4
    If lngTest = &H5244484D Then 'MHDR
        Call CopyMemory(mDH, ByVal Mid$(S, lngPos, Len(mDH)), Len(mDH))
        lngPos = lngPos + Len(mDH)
        mDH.FrameWidth = lngReverse(mDH.FrameWidth)
        mDH.FrameHeight = lngReverse(mDH.FrameHeight)
        If mDH.FrameWidth < 1 Or mDH.FrameHeight < 1 Then
            Debug.Print "MNG Bad Animation Size " & mDH.FrameWidth & "/" & mDH.FrameHeight
            Exit Function
        End If
    ElseIf lngTest = &H4D524554 Then 'TERM
        lngPos = lngPos + 18
    ElseIf lngTest = &H4B434142 Then 'BACK
        bBackCol = True
        Call CopyMemory(lngBackCol, ByVal Mid$(S, lngPos, 1) & Mid$(S, lngPos + 2, 1) & Mid$(S, lngPos + 4, 1), 3)
        lngPos = lngPos + 14
    ElseIf lngTest = &H44474B62 Then 'bKGD
        lngPos = lngPos + 14
    ElseIf lngTest = &H4D415246 Then 'FRAM
        lngPos = lngPos + 18
    ElseIf lngTest = &H454D4974 Then 'tIME
        lngPos = lngPos + 15
   
    ElseIf lngTest = &H52444849 Then 'IHDR (info)
        Call CopyMemory(pIH, ByVal Mid$(S, lngPos, Len(pIH)), Len(pIH))
        lngPos = lngPos + Len(pIH)
        pIH.Width = lngReverse(pIH.Width)
        pIH.Height = lngReverse(pIH.Height)
        If pIH.Width < 1 Or pIH.Height < 1 Then
            Debug.Print "MNG Bad Image Size " & pIH.Width & "/" & pIH.Height
            Exit Function
        End If
        If Not pIH.Bits = 4 And Not pIH.Bits = 8 Then  '8 bit only
            Debug.Print "MNG Bad File Bits " & pIH.Bits
            Exit Function
        End If
        If Not pIH.ColorType = 3 Then
            Debug.Print "MNG Bad Color type"
            Exit Function
        End If
       
    ElseIf lngTest = &H45544C50 Then 'PLTE (pallet)
        'check we got a header
        If Not pIH.Bits = 4 And Not pIH.Bits = 8 Then Exit Function
        lngTest = InStr(lngPos, S, "IDATx") - lngPos
        If lngTest < 1 Then
            Debug.Print "MNG No Pallet"
            Exit Function
        End If
        ReDim bPal(lngTest - 1)
        Call CopyMemory(bPal(0), ByVal Mid$(S, lngPos, lngTest), lngTest)
        lngPos = lngPos + lngTest
       
    ElseIf lngTest = &H54414449 Then 'IDAT (image data)
        If Not pIH.Bits = 4 And Not pIH.Bits = 8 Then Exit Function 'check we got header
        lngTest = InStr(lngPos, S, "IEND®B`,") - lngPos
        lngTest = lngTest - 4
        If lngTest < 1 Then
            Debug.Print "MNG No iEnd"
            Exit Function
        End If
        ReDim bData(lngTest - 1)
        ReDim bOut((((pIH.Width * pIH.Height) * 1.01) + 12))
        Call CopyMemory(bData(0), ByVal Mid$(S, lngPos, lngTest), lngTest)
        lngPos = lngPos + lngTest + 4
        Call uncompress(bOut(0), UBound(bOut) + 1, bData(0), lngTest)
       
        If pIH.Bits = 8 Then
            Call ClipLines(bOut(), pIH.Width, pIH.Height)
        ElseIf pIH.Bits = 4 Then
            Call ClipLines(bOut(), pIH.Width / 2, pIH.Height)
        End If
       
        Call SortPNGPallet8(bPal(), bBackCol, lngBackCol)
        ReDim Preserve bPal(UBound(bPal) + Len(pBM))
       
        pBM.biSize = Len(pBM)
        pBM.biPlanes = 1
        pBM.biBitCount = pIH.Bits
        pBM.biWidth = pIH.Width
        pBM.biHeight = pIH.Height
       
        '//Move pallet up 40 spaces
        Call CopyMemory(bPal(Len(pBM)), bPal(0), 1024)
        '//add info header
        Call CopyMemory(bPal(0), pBM, Len(pBM))
        '//Create the diBitmap
        BitMapCount = BitMapCount + 1
        ReDim Preserve BitMap(BitMapCount)
        BitMap(BitMapCount).Width = pBM.biWidth
        BitMap(BitMapCount).Height = pBM.biHeight
        BitMap(BitMapCount).OffX = 0
        BitMap(BitMapCount).OffY = 0
        lngTest = GetDC(0)
        BitMap(BitMapCount).Handle = CreateDIBitmap(lngTest, pBM, &H4, bOut(0), bPal(0), &H0)
        Call DeleteDC(lngTest)
       
    ElseIf lngTest = &H444E4549 Then 'IEND (end of image data)
        lngPos = lngPos + 8
       
    ElseIf lngTest = &H49464544 Then 'DEFI
        If BitMapCount >= 0 Then
            Call CopyMemory(BitMap(BitMapCount).OffX, ByVal Mid$(S, lngPos + 4, 4), 4)
            Call CopyMemory(BitMap(BitMapCount).OffY, ByVal Mid$(S, lngPos + 8, 4), 4)
            BitMap(BitMapCount).OffX = lngReverse(BitMap(BitMapCount).OffX)
            BitMap(BitMapCount).OffY = lngReverse(BitMap(BitMapCount).OffY)
        End If
        lngPos = lngPos + 20
       
    ElseIf lngTest = &H444E454D Then 'MEND (end of MNG)
        lngPos = lngPos + 8
        LoadMNG = (BitMapCount >= 0)
        Exit Function
       
    Else
        Debug.Print "Unknown MNG Header: " & MakeDWORD(lngTest)
        Exit Function
    End If
    GoTo LoadMNGNewHeader
   
End Function



Private Function lngReverse(ByVal lngValue As Long) As Long
    Dim S As String * 4
    Call CopyMemory(ByVal S, lngValue, 4)
    S = StrReverse(S)
    Call CopyMemory(lngReverse, ByVal S, 4)
End Function



Private Sub SortPNGPallet8(ByRef bPallet() As Byte, ByVal bBackGround As Boolean, _
    ByVal lngBackColor As Long)
    Dim i       As Integer
    Dim i2      As Integer
    Dim T(255)  As RGBTriple
    Dim Q(255)  As RGBQUAD
    Dim lngTest As Long
    Dim bTest   As Boolean
    ReDim Preserve bPallet(1023)
    Call CopyMemory(T(0), bPallet(0), 768)
    For i = 0 To 255
        Q(i).rgbRed = T(i).rgbBlue
        Q(i).rgbGreen = T(i).rgbGreen
        Q(i).rgbBlue = T(i).rgbRed
    Next i
    If bBackGround And (Not lngBackColor = 0) Then
        For i = 0 To 255
            Call CopyMemory(lngTest, Q(i), 3)
            If lngTest = lngBackColor Then
                If bTest = False Then
                    bTest = True
                Else
                    lngTest = 0
                    Call CopyMemory(Q(i), lngTest, 4)
                End If
            End If
        Next i
    End If
    Call CopyMemory(bPallet(0), Q(0), 1024)
End Sub


Private Sub ClipLines(ByRef bData() As Byte, ByVal lngBPL As Long, ByVal lngHeight As Long)
    Dim i           As Long
    Dim lngPos      As Long
    Dim bOut()      As Byte
    Dim lngWidth    As Long
    lngWidth = lngBPL
    If Not (lngWidth Mod 4) = 0 Then
        lngWidth = lngWidth + (4 - (lngWidth Mod 4))
    End If
    ReDim bOut(lngWidth - 1, lngHeight - 1)
    ReDim Preserve bData((lngWidth * lngHeight) - 1)
    For i = (lngHeight - 1) To 0 Step -1
        Call CopyMemory(bOut(0, i), bData(lngPos + 1), lngBPL)
        lngPos = lngPos + lngBPL + 1
    Next i
    ReDim bData((lngWidth * lngHeight) - 1)
    Call CopyMemory(bData(0), bOut(0, 0), (lngWidth * lngHeight))
End Sub

warz

it's not like the format to an mng file is secret, or anything. you can quit guessing, and just use google.

Ringo

Quote from: betawarz on March 22, 2008, 05:19 AM
it's not like the format to an mng file is secret, or anything. you can quit guessing, and just use google.
Yeah.. Ofc its not a secret, i never said it was :)
But i persionaly would rather try figger this stuff out rather than useing google, it makes good practiss and cures bordom.
Cant always rely on somthing like google to answer every question you have, what about when it cant answer it? :P
Just thought by shareing this, it might help some people with adding MNG support to their bots, is all.

Archangel.

Quote from: Ringo on March 22, 2008, 04:35 PM
Quote from: betawarz on March 22, 2008, 05:19 AM
it's not like the format to an mng file is secret, or anything. you can quit guessing, and just use google.
Yeah.. Ofc its not a secret, i never said it was :)
But i persionaly would rather try figger this stuff out rather than useing google, it makes good practiss and cures bordom.
Cant always rely on somthing like google to answer every question you have, what about when it cant answer it? :P
Just thought by shareing this, it might help some people with adding MNG support to their bots, is all.

lol easy, when google cant answer your question then you try to figure it out yourself, if you have any troubles then you ask here :).
aka: Archangel, i can't login into the account or request the password, weird problem.

Ringo

Quote from: AoD-Archangel on March 22, 2008, 11:59 PM
lol easy, when google cant answer your question then you try to figure it out yourself, if you have any troubles then you ask here :).

Well that might be yours and warez way of doing things, but i persionaly like to challenge my self :)
I didnt do this so I could load MNG's, nore so I could have mng support in a bot, I did it just to pass the time and challenge my self. As stated above, it makes good practiss.
People here probly have more use for this than I will, hence why I posted it.
Can we keep on subject, or can a mod trash every reply so far?