Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht Das deutsche QBasic- und FreeBASIC-Forum
Für euch erreichbar unter qb-forum.de, fb-forum.de und freebasic-forum.de!
 
FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen  RegistrierenRegistrieren
ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin
Zur Begleitseite des Forums / Chat / Impressum
Aktueller Forenpartner:

Animierte PNG Frames extrahieren 2025-07-31 beta [Windows]

 
Neues Thema eröffnen   Neue Antwort erstellen    Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht -> Projektvorstellungen
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
UEZ



Anmeldungsdatum: 24.06.2016
Beiträge: 140
Wohnort: Opel Stadt

BeitragVerfasst am: 31.07.2025, 20:10    Titel: Animierte PNG Frames extrahieren 2025-07-31 beta [Windows] Antworten mit Zitat

Code:

'Coded by UEZ in cooperation with ChatGPT ;-) build 2025-07-31 beta

#include "crt.bi"
#include "file.bi"
#ifdef __FB_64BIT__
    #inclib "gdiplus"
    #include once "win/gdiplus-c.bi"
#else
    #include once "win/gdiplus.bi"
    Using Gdiplus
#endif
#include "dir.bi"

#define CRLF    (Chr(13, 10))

' Initialize GDI+
Dim Shared gdipToken As ULONG_PTR
Dim Shared GDIp As GdiplusStartupInput
GDIp.GdiplusVersion = 1
If GdiplusStartup(@gdipToken, @GDIp, NULL) <> 0 Then End

' Save a GDI+ image to a file using appropriate encoder
Function _GDIPlus_ImageSaveToFile(hImage As Any Ptr, Filename As WString, JPGQual As ULong = 85) As Boolean '...'
    Dim As Single iW, iH
    If GdipGetImageDimension(hImage, @iW, @iH) <> 0 Then Return 0

    Dim As Byte iErr = 0
    Dim As ULong count, size
    GdipGetImageEncodersSize(@count, @size)

    Dim As CLSID clsid
    Dim As ImageCodecInfo Ptr pImageCodecInfo
    pImageCodecInfo = Allocate(size)
    GdipGetImageEncoders(count, size, pImageCodecInfo)

    #define _MimeType(x) (*Cast(WString Ptr, pImageCodecInfo[x].MimeType))
    #define FnSuffix (Right(Filename, 4))

    For i As ULong = 0 To count - 1
        ' Select correct encoder based on file extension
        If _MimeType(i) = "image/bmp" And FnSuffix = ".bmp" Then
            If GdipSaveImageToFile(hImage, WStr(Filename), @pImageCodecInfo[i].Clsid, NULL) <> 0 Then iErr += 1
        ElseIf _MimeType(i) = "image/jpeg" And (FnSuffix = ".jpg" Or FnSuffix = ".jpe" Or Right(Filename, 5) = ".jpeg" Or Right(Filename, 5) = ".jfif") Then
            ' Handle JPEG with quality
            JPGQual = IIf(JPGQual < 0, 0, IIf(JPGQual > 100, 100, JPGQual))
            Dim tParams As EncoderParameters
            Dim EncoderQuality As String = "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}"
            tParams.Count = 1
            CLSIDFromString(WStr(EncoderQuality), @tParams.Parameter(0).Guid)
            With tParams.Parameter(0)
                .NumberOfValues = 1
                .Type = EncoderParameterValueTypeLong
                .Value = VarPtr(JPGQual)
            End With
            If GdipSaveImageToFile(hImage, WStr(Filename), @pImageCodecInfo[i].Clsid, @tParams) <> 0 Then iErr += 1
        ElseIf _MimeType(i) = "image/gif" And FnSuffix = ".gif" Then
            If GdipSaveImageToFile(hImage, WStr(Filename), @pImageCodecInfo[i].Clsid, NULL) <> 0 Then iErr += 1
        ElseIf _MimeType(i) = "image/tiff" And (FnSuffix = ".tif" Or Right(Filename, 5) = ".tiff") Then
            If GdipSaveImageToFile(hImage, WStr(Filename), @pImageCodecInfo[i].Clsid, NULL) <> 0 Then iErr += 1
        ElseIf _MimeType(i) = "image/png" And FnSuffix = ".png" Then
            If GdipSaveImageToFile(hImage, WStr(Filename), @pImageCodecInfo[i].Clsid, NULL) <> 0 Then iErr += 1
        End If
    Next

    Deallocate(pImageCodecInfo)

    If iErr > 0 Then Return False
    Return True
End Function

Function _GDIPlus_BitmapCreateFromMemory3(aBinImage As UByte Ptr, iLen As ULong, bBitmap_GDI As BOOL = False, iCol_GDI As ULong = &hFF000000) As Any Ptr '...'
   Dim As HGLOBAL hGlobal
   Dim As LPSTREAM hStream
   Dim As Any Ptr hImage_Stream
   Dim As Any Ptr hMemory = GlobalAlloc(GMEM_MOVEABLE, iLen)
   Dim As Any Ptr lpMemory = GlobalLock(hMemory)
   RtlCopyMemory(lpMemory, @aBinImage[0], iLen)
   GlobalUnlock(hMemory)
   CreateStreamOnHGlobal(hMemory, False, @hStream)
   GdipCreateBitmapFromStream(hStream, @hImage_Stream)
   IUnknown_Release(hStream)
   GlobalFree(hMemory)
   
   If bBitmap_GDI = True Then
      Dim hImage_GDI As Any Ptr
      GdipCreateHBITMAPFromBitmap(hImage_Stream, @hImage_GDI, iCol_GDI)
      GdipDisposeImage(hImage_Stream)
      Return hImage_GDI
   EndIf

   Return hImage_Stream
End Function

'------------------------------------------------------------------------------
' CRC32 support: initialize table and calculate CRC over a byte stream
'------------------------------------------------------------------------------
Dim Shared As UInteger CRC_TABLE(0 To 255)
Dim Shared As Boolean  CRC_TABLE_INITIALIZED = False

'----------------------------------------------------
' Initializes the CRC32 lookup table (only once)
' This speeds up CRC32 calculation by avoiding repeated bitwise computation
'----------------------------------------------------
Sub InitCRCTable() '...'
    ' Skip initialization if already done
    If CRC_TABLE_INITIALIZED Then Exit Sub

    ' The CRC32 polynomial used in many implementations (standard)
    Dim As UInteger poly = &hEDB88320
    Dim As UInteger i, j, c

    ' Fill the table with 256 precomputed CRC values for all byte values (0–255)
    For i = 0 To 255
        c = i
        For j = 0 To 7
            ' Shift bitwise and apply polynomial if needed
            If (c And 1) Then
                c = (c Shr 1) Xor poly
            Else
                c Shr= 1
            End If
        Next
        ' Store the result in the lookup table
        CRC_TABLE(i) = c
    Next

    ' Mark table as initialized
    CRC_TABLE_INITIALIZED = True
End Sub

'----------------------------------------------------
' Calculates the CRC32 checksum over a byte stream
'----------------------------------------------------
Function CRC32(initial As UInteger, bufPtr As Any Ptr, length As UInteger) As UInteger '...'
    ' Ensure the CRC lookup table is initialized (only done once)
    InitCRCTable()

    ' Cast the generic pointer to a byte pointer for array-style access
    Dim As UByte Ptr b = bufPtr

    ' Initialize CRC value by XORing with 0xFFFFFFFF
    Dim As UInteger c = initial Xor &hFFFFFFFF

    ' Process each byte in the buffer
    For i As UInteger = 0 To length - 1
        ' Update CRC using lookup table and shift
        c = CRC_TABLE((c Xor b[i]) And &hFF) Xor (c Shr 8)
    Next

    ' Final XOR to get the resulting CRC value
    Return c Xor &hFFFFFFFF
End Function


' Reads a big-endian 32-bit unsigned integer from file
Function ReadUInt32BE(ByRef hFile As Long) As UInteger '...'
    Dim b(3) As UByte
    Get #hFile, , b()
    Return (b(0) Shl 24) Or (b(1) Shl 16) Or (b(2) Shl 8) Or b(3)
End Function

'--------------------------------------------
' Appends bytes from src() to dest()
'--------------------------------------------
Sub AppendBytes(dest() As UByte, src() As UByte) '...'
    Dim oldLen As Integer
    If UBound(dest) >= 0 Then
        oldLen = UBound(dest) + 1
    Else
        oldLen = 0
    End If
    ReDim Preserve dest(oldLen + UBound(src))
    For i As UInteger = 0 To UBound(src)
        dest(oldLen + i) = src(i)
    Next
End Sub

'--------------------------------------------
' Creates a full PNG chunk (length, type, data, CRC)
' Returns as Byte() to append to PNG
'--------------------------------------------
Sub GetChunkBytes(name_ As String, chunk() As UByte, result() As UByte)
     Dim length As UInteger = UBound(chunk) - LBound(chunk) + 1

    ' Length (4 bytes, big-endian)
    ReDim result(3)
    result(0) = (length Shr 24) And &hFF
    result(1) = (length Shr 16) And &hFF
    result(2) = (length Shr  8) And &hFF
    result(3) = (length       ) And &hFF

    ' Chunk type (4 bytes)
    Dim typeBytes(3) As UByte
    For i As UByte = 0 To 3
        typeBytes(i) = Asc(Mid(name_, i + 1, 1))
    Next
    AppendBytes(result(), typeBytes())

    ' Chunk data
    If length > 0 Then AppendBytes(result(), chunk())

    ' CRC
    Dim crcVal As UInteger = 0
    crcVal = CRC32(crcVal, VarPtr(typeBytes(0)), 4)
    If length > 0 Then crcVal = CRC32(crcVal, VarPtr(chunk(0)), length)

    Dim crcBytes(3) As UByte
    crcBytes(0) = (crcVal Shr 24) And &hFF
    crcBytes(1) = (crcVal Shr 16) And &hFF
    crcBytes(2) = (crcVal Shr  8) And &hFF
    crcBytes(3) = (crcVal       ) And &hFF
    AppendBytes(result(), crcBytes())

End Sub

'----------------------------------------------------
' Writes a single PNG frame to memory: IHDR, IDAT, IEND
' Optionally recodes the image using GDI+ to fix offsets
'----------------------------------------------------
Sub WritePNGFrame_Memory(idx As Integer, ihdr() As UByte, idat() As UByte, delayMS As Integer, padLen As Integer, _
    bRecodePNG As BOOL = False, fixedWidth As ULong = 0, fixedHeight As ULong = 0, x_offset As Long = 0, y_offset As Long = 0, _
    sExtractedPath As String = ExePath & "\ExtractedFrames")

    ' Filename
    Dim numStr As String = Right("000" & LTrim(Str(idx + 1)), padLen)
    Dim filename As String = IIf(sExtractedPath <> "", sExtractedPath & "\Frame_" & numStr & "_" & delayMS & "ms.png", CurDir & "\Frame_" & numStr & "_" & delayMS & "ms.png")
   
    ' 1) PNG header
    Dim sig(7) As UByte = { &h89, &h50, &h4E, &h47, &hD, &hA, &h1A, &hA }
    Dim fullPNG() As UByte
    ' ReDim it to the size of the PNG signature and copy the signature bytes
    ReDim fullPNG(7)
    For i As Integer = 0 To 7
        fullPNG(i) = sig(i)
    Next

    ' 2) IHDR
    Dim chunkIHDR() As UByte
    GetChunkBytes("IHDR", ihdr(), chunkIHDR())
    AppendBytes(fullPNG(), chunkIHDR())

    ' 3) IDAT
    Dim chunkIDAT() As UByte
    GetChunkBytes("IDAT", idat(), chunkIDAT())
    AppendBytes(fullPNG(), chunkIDAT())

    ' 4) IEND
    Dim emptyBuf(0) As UByte
    Dim chunkIEND() As UByte
    GetChunkBytes("IEND", emptyBuf(), chunkIEND())
    AppendBytes(fullPNG(), chunkIEND())

    ' Optional recoding using memory (no temp file)
    If bRecodePNG Then
        ' Load PNG directly from memory
        Dim hStreamImage As Any Ptr
        hStreamImage = _GDIPlus_BitmapCreateFromMemory3(@fullPNG(0), UBound(fullPNG) + 1)

        If hStreamImage = 0 Then
            printf("Error: failed to create image from memory!" & CRLF)
            Exit Sub
        End If

        ' Prepare canvas
        Dim As Any Ptr hCanvas, hBitmap
        Dim As Single iw, ih
        GdipCreateBitmapFromScan0(fixedWidth, fixedHeight, 0, PixelFormat32bppARGB, 0, @hBitmap)
        GdipGetImageGraphicsContext(hBitmap, @hCanvas)
        GdipGetImageDimension(hStreamImage, @iw, @ih)
        GdipDrawImageRect(hCanvas, hStreamImage, x_offset, y_offset, iw, ih)

        ' Save corrected PNG
        If _GDIPlus_ImageSaveToFile(hBitmap, filename) = False Then
            printf("Error: saving recoded PNG failed!" & CRLF)
        End If

        ' Cleanup
        GdipDisposeImage(hStreamImage)
        GdipDeleteGraphics(hCanvas)
        GdipDisposeImage(hBitmap)
    Else
        ' Save directly without recoding
        Dim hOut As Long = FreeFile()
        Open filename For Binary As #hOut
        Put #hOut, , fullPNG()
        Close #hOut
    End If
End Sub

Function ExtractAPNG(ByVal path As String, sExtractedPath As String = ExePath & "\ExtractedFrames", iDefaultDelay As UShort = 50) As Integer
    ' Check if the file exists
    If FileExists(path) = 0 Then
        printf("File not found: " & path & CRLF)
        Return -1
    End If

    ' --- Phase 1: Count fcTL chunks (animation frame control chunks) ---
    Dim As Long iFileAPNG = FreeFile()
    Open path For Binary As #iFileAPNG
    Dim sig(7) As UByte : Get #iFileAPNG, , sig()
    Dim As Long total = 0
    Dim As UInteger iLen
    Dim As String t
    Do Until EOF(iFileAPNG)
        ' Read the length and type of the next chunk
        iLen = ReadUInt32BE(iFileAPNG)
        Dim tbuf(3) As UByte : Get #iFileAPNG, , tbuf()
        t = Chr(tbuf(0), tbuf(1), tbuf(2), tbuf(3))
        If t = "fcTL" Then total += 1
        ' Skip the chunk data and CRC
        Seek #iFileAPNG, Seek(iFileAPNG) + iLen + 4
    Loop
    Close #iFileAPNG

    ' If less than 2 frames, it's not an animated PNG
    If total < 2 Then
        printf("No animation chunks found." & CRLF)
        Return -2
    End If
    Dim As Integer AttrTester
    If sExtractedPath <> "" AndAlso Dir(sExtractedPath, (fbReadOnly Or fbHidden Or fbSystem Or fbDirectory Or fbArchive), AttrTester) = "" Then
       If MkDir(sExtractedPath) = -1 Then
          printf("Unable to create " & sExtractedPath & CRLF)
          Return -3
       End If
    End If

    Dim As Long padLen = Len(Str(total)) ' Determine zero-padding length for filenames

    ' --- Phase 2: Parse and extract individual frames ---
    iFileAPNG = FreeFile()
    Open path For Binary As #iFileAPNG

    ' Skip the 8-byte PNG signature
    Dim sig2(7) As UByte
    Get #iFileAPNG, , sig2()

    Dim As Boolean gotFirstFrameProcessed = False
    Dim As Integer frameIdx = 0, curDelay = 50, bufLen
    Dim As UByte bufAll(), ihdr(), temp()
    Dim As Long w = 0, h = 0, i, num, den, skip, pLen
    Dim As ULong fixedWidth, fixedHeight, current_x_offset, current_y_offset
 
    Do Until EOF(iFileAPNG)
        ' Read chunk length and type
        iLen = ReadUInt32BE(iFileAPNG)
      If iLen > LOF(iFileAPNG) - Seek(iFileAPNG) Then
          printf("Invalid chunk size at position " & Seek(iFileAPNG) & CRLF)
          Close #iFileAPNG
          Return -4
      End If
        Dim tbuf(3) As UByte
        Get #iFileAPNG, , tbuf()
        t = Chr(tbuf(0), tbuf(1), tbuf(2), tbuf(3))

        Select Case t
            Case "IHDR"
                ' Read IHDR chunk (image header)
                ReDim ihdr(iLen - 1)
                Get #iFileAPNG, , ihdr()
                Seek #iFileAPNG, Seek(iFileAPNG) + 4 ' Skip CRC

           Case "fcTL"
             If iLen <> 26 Then
                 printf("Invalid fcTL chunk size." & CRLF)
                 Close #iFileAPNG
                 Return -4
             End If
                ' If a previous frame exists, write it to a PNG file
                If gotFirstFrameProcessed And bufLen > 0 Then
                    ReDim temp(bufLen - 1)
                    For i = 0 To bufLen - 1
                        temp(i) = bufAll(i)
                    Next
                    If w <> fixedWidth Or h <> fixedHeight Then
                        WritePNGFrame_Memory(frameIdx, ihdr(), temp(), curDelay, padLen, True, fixedWidth, fixedHeight, current_x_offset, current_y_offset)
                    Else
                        WritePNGFrame_Memory(frameIdx, ihdr(), temp(), curDelay, padLen)
                    End If
                    frameIdx += 1
                End If

                ' Read frame control chunk (fcTL) - 26 bytes
                Dim ctl(25) As UByte
                Get #iFileAPNG, , ctl()
                Seek #iFileAPNG, Seek(iFileAPNG) + 4 ' Skip CRC

                ' Parse frame width, height, and offset
                w = (ctl(4) Shl 24) Or (ctl(5) Shl 16) Or (ctl(6) Shl 8) Or ctl(7)
                h = (ctl(8) Shl 24) Or (ctl(9) Shl 16) Or (ctl(10) Shl 8) Or ctl(11)
                current_x_offset = (ctl(12) Shl 24) Or (ctl(13) Shl 16) Or (ctl(14) Shl 8) Or ctl(15)
                current_y_offset = (ctl(16) Shl 24) Or (ctl(17) Shl 16) Or (ctl(18) Shl 8) Or ctl(19)

                If Not gotFirstFrameProcessed Then
                    gotFirstFrameProcessed = True
                    fixedWidth = w
                    fixedHeight = h
                End If

                ' Update IHDR width and height
                ihdr(0) = (w Shr 24) And &HFF
                ihdr(1) = (w Shr 16) And &HFF
                ihdr(2) = (w Shr 8) And &HFF
                ihdr(3) =  w And &HFF
                ihdr(4) = (h Shr 24) And &HFF
                ihdr(5) = (h Shr 16) And &HFF
                ihdr(6) = (h Shr 8) And &HFF
                ihdr(7) =  h And &HFF

                ' Read delay numerator and denominator
                num = (ctl(20) Shl 8) Or ctl(21)
                den = (ctl(22) Shl 8) Or ctl(23)
                If den = 0 Then den = iDefaultDelay ' Default denominator
                curDelay = (num * 1000) \ den ' Convert to milliseconds

                bufLen = 0 ' Reset image data buffer

            Case "IDAT", "fdAT"
                ' Collect image data for this frame (either IDAT or fdAT)
                Dim tmp(iLen - 1) As UByte
                Get #iFileAPNG, , tmp()
                skip = IIf(t = "fdAT", 4, 0) ' Skip sequence number for fdAT
                pLen = iLen - skip
                If bufLen = 0 Then
                    ReDim bufAll(pLen - 1)
                Else
                    ReDim Preserve bufAll(bufLen + pLen - 1)
                End If
                For i = 0 To pLen - 1
                    bufAll(bufLen + i) = tmp(i + skip)
                Next
                bufLen += pLen
                Seek #iFileAPNG, Seek(iFileAPNG) + 4 ' Skip CRC

            Case "IEND"
                ' Finalize and write the last frame
                If bufLen > 0 Then
                    Dim t2(bufLen - 1) As UByte
                    For i = 0 To bufLen - 1
                        t2(i) = bufAll(i)
                    Next
                    If w <> fixedWidth Or h <> fixedHeight Then
                        WritePNGFrame_Memory(frameIdx, ihdr(), t2(), curDelay, padLen, True, fixedWidth, fixedHeight, current_x_offset, current_y_offset)
                    Else
                        WritePNGFrame_Memory(frameIdx, ihdr(), t2(), curDelay, padLen)
                    End If
                    frameIdx += 1
                End If
                Exit Do

            Case Else
                ' Skip unknown chunks
                Seek #iFileAPNG, Seek(iFileAPNG) + iLen + 4
        End Select
    Loop

    Close #iFileAPNG
    Return frameIdx
End Function

Declare Function URLDownloadToFile2 Lib "urlmon" Alias "URLDownloadToFileA" ( _
                                                             ByVal pCaller As Any Ptr, _
                                                             ByVal szURL As ZString Ptr, _
                                                             ByVal szFileName As ZString Ptr, _
                                                             ByVal dwReserved As Long, _
                                                             ByVal lpfnCB As Any Ptr _
                                                         ) As Long
' ---------- MAIN ----------
'Download example file from internet and extract frames
Dim As String localFile
If Len(Command(1)) Then
   localFile = Command(1)
Else
   localFile = ExePath & "\Animated_PNG_example_bouncing_beach_ball.png"
   If FileExists(localFile) = 0 Then
      Dim As String url = "https://upload.wikimedia.org/wikipedia/commons/1/14/Animated_PNG_example_bouncing_beach_ball.png"
      URLDownloadToFile2(NULL, StrPtr(url), StrPtr(localFile), 0, NULL)
   End If
End If

If FileExists(localFile) = 0 Then
   printf(localFile & " not found!" & CRLF)
   Sleep
   End 0
End If
Dim As ULong iFrameCount = ExtractAPNG(localFile)
If iFrameCount > 1 Then
   printf("Done: " & iFrameCount  & " frames extracted from > " & localFile & " <." & CRLF)
Else
   printf("Something went wrong extracting frames from > " & localFile & " <!" & CRLF)
End If

GdiplusShutdown(gdipToken)
Sleep


Eine Test APNG Animation wird heruntergeladen und die Frames extrahiert.
_________________
Gruß,
UEZ
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    Das deutsche QBasic- und FreeBASIC-Forum Foren-Übersicht -> Projektvorstellungen Alle Zeiten sind GMT + 1 Stunde
Seite 1 von 1

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.

 Impressum :: Datenschutz