Moin,
anbei ist eine 1:1 Übersetzung der C++ Implementierung von https://github.com/phoboslab/qoi/blob/master/qoi.h
Falls ich dabei keine Abschreibefehler gemacht habe (unwahrscheinlich, aber möglich
) wäre das hier eine "standard" Implementierung vom QOI Dateiformat für Bilder mit Transparenz.
Für eine anschauliche Erklärung von QOI kann ich das hier empfehlen: youtube dot com/watch?v=EFUYNoFRHQI (nicht vom Titel verwirren lassen, QOI wird hier auch behandelt)
PS: Code ist nicht optimiert, war auch nicht das Ziel ![]()
#include-once
; QOI AutoIt reference implementation by Mars
; https://github.com/phoboslab/qoi/blob/master/qoi.h
Global Const $QOI_SRGB = 0x00
Global Const $QOI_LINEAR = 0x01
Global Const $QOI_OP_INDEX = 0x00 ; 00xxxxxx
Global Const $QOI_OP_DIFF = 0x40 ; 01xxxxxx
Global Const $QOI_OP_LUMA = 0x80 ; 10xxxxxx
Global Const $QOI_OP_RUN = 0xc0 ; 11xxxxxx
Global Const $QOI_OP_RGB = 0xfe ; 11111110
Global Const $QOI_OP_RGBA = 0xff ; 11111111
Global Const $QOI_MASK_2 = 0xC0 ; 11000000
Global Const $QOI_PIXELS_MAX = 400000000 ; max file size = 2GB
Global Const $QOI_MAGIC = BitShift(Asc('q'), -24) + BitShift(Asc('o'), -16) + BitShift(Asc('i'), -8) + Asc('f') ; 'qoif' = 1903128945 (LE)
Global Const $QOI_HEADER_SIZE = 14
Global Const $QOI_PADDING = [0, 0, 0, 0, 0, 0, 0, 1]
Global Const $QOI_CH_RGB = 3
Global Const $QOI_CH_ARGB = 4
; Helper because AutoIt has no direct byte access otherwise
Global $__QOI_RGBA = __QOI_Pixel()
Global $__QOI_UI32 = __QOI_i32At($__QOI_RGBA)
; Internal byte order is fixed at 3, 0, 1, 2
Global $__QOI_ExtARGBByteOrder = [3, 0, 1, 2]
Func _QOI_ExtARGBByteOrder($a = 3, $r = 0, $g = 1, $b = 2)
Local $_ = [$a, $r, $g, $b]
$__QOI_ExtARGBByteOrder = $_
EndFunc
; Get strings for error-codes
Func _QOI_Error($i)
Switch $i
Case 0
Return 'Ok'
Case 1
Return '_QOI_Encode/_QOI_Decode: $vData is no DllStruct'
Case 2
Return '_QOI_Encode/_QOI_Decode: $vHeader is no DllStruct'
Case 3
Return '_QOI_Encode/_QOI_Decode: $vLen is no DllStruct'
Case 4
Return '_QOI_Encode/_QOI_Decode: Invalid header'
Case 5
Return '_QOI_Decode: Invalid size in $vLen'
Case 6
Return '_QOI_Decode: Invalid magic number in $vData'
Case 10 To 20
Return '_QOI_Encode: Alloc failed: DllStructCreate @error = ' & ($i - 10)
Case 100
Return '_QOI_Encode: Unknown error'
Case 101
Return '_QOI_Write: Cannot open File in binary write mode'
EndSwitch
Return 'Unknown error code: ' & $i
EndFunc
; All these must be specified when writing files
Func _QOI_Header($ui32_width = 0, $ui32_height = 0, $ui8_channels = 0, $ui8_colorspace = 0)
Local $vHeader = DllStructCreate('uint width;uint height; byte channels; byte colorspace')
$vHeader.width = $ui32_width
$vHeader.height = $ui32_height
$vHeader.channels = $ui8_channels
$vHeader.colorspace = $ui8_colorspace
Return $vHeader
EndFunc
Func _QOI_Encode($vData, $vHeader, $vLen)
; Ist direkt aus C++ übersetzt und nicht für AutoIt optimiert!
If Not IsDllStruct($vData) Then Return SetError(1, 0, 0)
If Not IsDllStruct($vHeader) Then Return SetError(2, 0, 0)
If Not IsDllStruct($vLen) Then Return SetError(3, 0, 0)
If $vHeader.width = 0 Or $vHeader.height = 0 Or _
$vHeader.channels < 3 Or $vHeader.channels > 4 Or _
$vHeader.colorspace > 1 Or _
$vHeader.height >= Int($QOI_PIXELS_MAX / $vHeader.width) _
Then Return SetError(4, 0, 0) ; Invalid header
Local Const $iMaxSize = $vHeader.width * $vHeader.height * ($vHeader.channels + 1) + $QOI_HEADER_SIZE + UBound($QOI_PADDING)
Local $vBytes = DllStructCreate('byte[' & $iMaxSize & ']')
If @error Then Return SetError(@error + 10, 0, 0) ; Alloc failed
Local Const $pBytes = DllStructGetPtr($vBytes)
Local $p = 0 ; Pointer & Index
__QOI_write_ui32($pBytes, $p, $QOI_MAGIC)
__QOI_write_ui32($pBytes, $p, $vHeader.width)
__QOI_write_ui32($pBytes, $p, $vHeader.height)
__QOI_write_ui8($pBytes, $p, $vHeader.channels)
__QOI_write_ui8($pBytes, $p, $vHeader.colorspace)
Local Const $pPixels = DllStructGetPtr($vData)
Local $avIndex[64], $aiIndex[64]
For $i = 0 To UBound($avIndex) - 1 Step 1
$avIndex[$i] = __QOI_Pixel()
$aiIndex[$i] = __QOI_i32At($avIndex[$i])
Next
Local $iRun = 0, $iIndexPos = 0
Local $vPx = __QOI_PixelVal(0, 0, 0, 255)
Local $iPx = __QOI_i32At($vPx)
Local $vPxPrev = __QOI_PixelVal(0, 0, 0, 255)
Local $iPxPrev = __QOI_i32At($vPxPrev)
Local Const $iPxLen = $vHeader.width * $vHeader.height * $vHeader.channels
Local Const $iPxEnd = $iPxLen - $vHeader.channels
Local Const $iChannels = $vHeader.channels
Local $vr = 0, $vg = 0, $vb = 0, $vgr = 0, $vgb = 0
For $iPxPos = 0 To $iPxLen - 1 Step $iChannels
$vPx.r = __QOI_peek_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[1])
$vPx.g = __QOI_peek_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[2])
$vPx.b = __QOI_peek_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[3])
If $iChannels = 4 Then $vPx.a = __QOI_peek_ui8($pPixels, $iPxPos + + $__QOI_ExtARGBByteOrder[0])
If $iPx.v == $iPxPrev.v Then
$iRun += 1
If $iRun = 62 Or $iPxPos = $iPxEnd Then
__QOI_write_ui8($pBytes, $p, BitOR($QOI_OP_RUN, $iRun - 1))
$iRun = 0
EndIf
Else
If $iRun > 0 Then
__QOI_write_ui8($pBytes, $p, BitOR($QOI_OP_RUN, $iRun - 1))
$iRun = 0
EndIf
$iIndexPos = BitAND(__QOI_hash_ui32($vPx), 64 - 1)
If $aiIndex[$iIndexPos].v = $iPx.v Then
__QOI_write_ui8($pBytes, $p, BitOR($QOI_OP_INDEX, $iIndexPos))
Else
$aiIndex[$iIndexPos].v = $iPx.v
If $vPx.a = $vPxPrev.a Then
$vr = $vPx.r - $vPxPrev.r
$vg = $vPx.g - $vPxPrev.g
$vb = $vPx.b - $vPxPrev.b
$vgr = $vr - $vg
$vgb = $vb - $vg
If $vr > -3 And $vr < 2 And $vg > -3 And $vg < 2 And $vb > -3 And $vb < 2 Then
__QOI_write_ui8($pBytes, $p, BitOR($QOI_OP_DIFF, BitShift($vr + 2, -4), BitShift($vg + 2, -2), $vb + 2))
ElseIf $vgr > -9 And $vgr < 8 And $vg > -33 And $vg < 32 And $vgb > -9 And $vgb < 8 Then
__QOI_write_ui8($pBytes, $p, BitOR($QOI_OP_LUMA, $vg + 32))
__QOI_write_ui8($pBytes, $p, BitOR(BitShift($vgr + 8, -4), $vgb + 8))
Else
__QOI_write_ui8($pBytes, $p, $QOI_OP_RGB)
__QOI_write_ui8($pBytes, $p, $vPx.r)
__QOI_write_ui8($pBytes, $p, $vPx.g)
__QOI_write_ui8($pBytes, $p, $vPx.b)
EndIf
Else
__QOI_write_ui8($pBytes, $p, $QOI_OP_RGBA)
__QOI_write_ui8($pBytes, $p, $vPx.r)
__QOI_write_ui8($pBytes, $p, $vPx.g)
__QOI_write_ui8($pBytes, $p, $vPx.b)
__QOI_write_ui8($pBytes, $p, $vPx.a)
EndIf
EndIf
EndIf
$iPxPrev.v = $iPx.v
Next
For $i = 0 To UBound($QOI_PADDING) - 1 Step 1
__QOI_write_ui8($pBytes, $p, $QOI_PADDING[$i])
Next
DllStructSetData($vLen, 1, $p)
Return $vBytes
EndFunc
Func _QOI_Write($sFileName, $vData, $vHeader)
Local $vLen = DllStructCreate('uint')
Local $vEncoded = _QOI_Encode($vData, $vHeader, $vLen)
If @error Then Return SetError(@error, 0, 1)
If DllStructGetData($vLen, 1) = 0 Then Return SetError(100, 0, 0)
Local Const $FO_OVERWRITE = 2
Local Const $FO_BINARY = 16
Local $hFile = FileOpen($sFileName, $FO_BINARY + $FO_OVERWRITE)
If $hFile = -1 Then Return SetError(101, 0, 0)
Local $vFile = DllStructCreate('byte[' & DllStructGetData($vLen, 1) & ']', DllStructGetPtr($vEncoded))
FileWrite($hFile, DllStructGetData($vFile, 1))
FileClose($hFile)
Return DllStructGetData($vLen, 1)
EndFunc
Func _QOI_Decode($vData, $vHeader, $vLen)
If Not IsDllStruct($vData) Then Return SetError(1, 0, 0)
If Not IsDllStruct($vHeader) Then Return SetError(2, 0, 0)
If Not IsDllStruct($vLen) Then Return SetError(3, 0, 0)
Local $iSize = DllStructGetData($vLen, 1)
If $iSize < $QOI_HEADER_SIZE + UBound($QOI_PADDING) Then Return SetError(5, 0, 0)
Local Const $pBytes = DllStructGetPtr($vData)
Local $p = 0 ; Pointer & Index
Local $iChannels = $vHeader.channels ; Output channels (can be different from input e.g. when reading a RGB qoi into 32bit ARGB aligned mem or vice versa)
Local Const $iMagic = __QOI_read_ui32($pBytes, $p)
If $iMagic <> $QOI_MAGIC Then Return SetError(6, 0, 0)
$vHeader.width = __QOI_read_ui32($pBytes, $p)
$vHeader.height = __QOI_read_ui32($pBytes, $p)
$vHeader.channels = __QOI_read_ui8($pBytes, $p)
$vHeader.colorspace = __QOI_read_ui8($pBytes, $p)
If $vHeader.width = 0 Or $vHeader.height = 0 Or _
$vHeader.channels < 3 Or $vHeader.channels > 4 Or _
$vHeader.colorspace > 1 Or _
$vHeader.height >= Int($QOI_PIXELS_MAX / $vHeader.width) _
Then Return SetError(4, 0, 0) ; Invalid header
If $iChannels = 0 Then $iChannels = $vHeader.channels
Local Const $iPxLen = $vHeader.width * $vHeader.height * $iChannels
Local $vPixels = DllStructCreate('byte[' & $iPxLen & ']')
If @error Then Return SetError(@error + 10, 0, 0) ; Alloc failed
Local Const $pPixels = DllStructGetPtr($vPixels)
Local $avIndex[64], $aiIndex[64]
For $i = 0 To UBound($avIndex) - 1 Step 1
$avIndex[$i] = __QOI_Pixel()
$aiIndex[$i] = __QOI_i32At($avIndex[$i])
Next
Local $vPx = __QOI_PixelVal(0, 0, 0, 255)
Local $iPx = __QOI_i32At($vPx)
Local $iChunksLen = $iSize - UBound($QOI_PADDING)
Local $iRun = 0, $iB1 = 0, $iIndexPos = 0, $iB2 = 0, $vg = 0
For $iPxPos = 0 To $iPxLen - 1 Step $iChannels
If $iRun > 0 Then
$iRun -= 1
ElseIf $p < $iChunksLen Then
$iB1 = __QOI_read_ui8($pBytes, $p)
If $iB1 = $QOI_OP_RGB Then
$vPx.r = __QOI_read_ui8($pBytes, $p)
$vPx.g = __QOI_read_ui8($pBytes, $p)
$vPx.b = __QOI_read_ui8($pBytes, $p)
ElseIf $iB1 = $QOI_OP_RGBA Then
$vPx.r = __QOI_read_ui8($pBytes, $p)
$vPx.g = __QOI_read_ui8($pBytes, $p)
$vPx.b = __QOI_read_ui8($pBytes, $p)
$vPx.a = __QOI_read_ui8($pBytes, $p)
ElseIf BitAND($iB1, $QOI_MASK_2) = $QOI_OP_INDEX Then
$iIndexPos = $iB1 ; Kein BitAnd 63 erforderlich, da Mask == 0b00111111
$iPx.v = $aiIndex[$iIndexPos].v
ElseIf BitAND($iB1, $QOI_MASK_2) = $QOI_OP_DIFF Then
$vPx.r += BitAND(BitShift($iB1, 4), 0x03) - 2
$vPx.g += BitAND(BitShift($iB1, 2), 0x03) - 2
$vPx.b += BitAND($iB1, 0x03) - 2
ElseIf BitAND($iB1, $QOI_MASK_2) = $QOI_OP_LUMA Then
$iB2 = __QOI_read_ui8($pBytes, $p)
$vg = BitAND($iB1, 0x3F) - 32
$vPx.r += $vg - 8 + BitAND(BitShift($iB2, 4), 0x0F)
$vPx.g += $vg
$vPx.b += $vg - 8 + BitAND($iB2, 0x0F)
ElseIf BitAND($iB1, $QOI_MASK_2) = $QOI_OP_RUN Then
$iRun = BitAND($iB1, 0x3F)
EndIf
$iIndexPos = BitAND(__QOI_hash_ui32($vPx), 64 - 1)
$aiIndex[$iIndexPos].v = $iPx.v
EndIf
__QOI_put_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[1], $vPx.r)
__QOI_put_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[2], $vPx.g)
__QOI_put_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[3], $vPx.b)
If $iChannels = 4 Then __QOI_put_ui8($pPixels, $iPxPos + $__QOI_ExtARGBByteOrder[0], $vPx.a)
Next
Return $vPixels
EndFunc
Func _QOI_Read($sFileName, ByRef $vData, $vHeader)
Local Const $FO_BINARY = 16
Local $hFile = FileOpen($sFileName, $FO_BINARY)
If $hFile = -1 Then Return SetError(101, 0, 0)
Local $vLen = DllStructCreate('uint')
DllStructSetData($vLen, 1, FileGetSize($sFileName))
Local $vBytes = DllStructCreate('byte[' & DllStructGetData($vLen, 1) & ']')
DllStructSetData($vBytes, 1, FileRead($hFile))
FileClose($hFile)
$vData = _QOI_Decode($vBytes, $vHeader, $vLen)
EndFunc
#region INTERNAL
Func __QOI_write_ui8($pBytes, ByRef $iPos, $ui8_value)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitAND($ui8_value, 0x000000FF)
$iPos += 1
EndFunc
Func __QOI_put_ui8($pBytes, $iPos, $ui8_value)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitAND($ui8_value, 0x000000FF)
EndFunc
Func __QOI_read_ui8($pBytes, ByRef $iPos)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
$iPos += 1
Return $v.v
EndFunc
Func __QOI_peek_ui8($pBytes, $iPos)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
Return $v.v
EndFunc
Func __QOI_write_ui32($pBytes, ByRef $iPos, $i32_value)
; Ist direkt aus C++ übersetzt und nicht für AutoIt optimiert!
; Da nacheinander geschrieben wird ist das endian safe (ist es bei AutoIt sowieso, da Windows only)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitShift(BitAND($i32_value, 0xFF000000), 24)
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitShift(BitAND($i32_value, 0x00FF0000), 16)
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitShift(BitAND($i32_value, 0x0000FF00), 8)
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
$v.v = BitAND($i32_value, 0x000000FF)
$iPos += 1
EndFunc
Func __QOI_read_ui32($pBytes, ByRef $iPos)
; Ist direkt aus C++ übersetzt und nicht für AutoIt optimiert!
; Da nacheinander gelesen wird ist das endian safe (ist es bei AutoIt sowieso, da Windows only)
Local $v = DllStructCreate('byte v', $pBytes + $iPos)
Local $a = $v.v
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
Local $b = $v.v
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
Local $c = $v.v
$iPos += 1
$v = DllStructCreate('byte v', $pBytes + $iPos)
Local $d = $v.v
$iPos += 1
Return BitOR(BitShift($a, -24), BitShift($b, -16), BitShift($c, -8), $d)
EndFunc
Func __QOI_Pixel()
Return DllStructCreate('byte r; byte g; byte b; byte a')
EndFunc
Func __QOI_PixelVal($r, $g, $b, $a)
Local $vPx = DllStructCreate('byte r; byte g; byte b; byte a')
$vPx.r = $r
$vPx.g = $g
$vPx.b = $b
$vPx.a = $a
Return $vPx
EndFunc
Func __QOI_i32At($vStruct)
Return DllStructCreate('uint v', DllStructGetPtr($vStruct))
EndFunc
Func __QOI_hash_ui32($vPx)
Return $vPx.r * 3 + $vPx.g * 5 + $vPx.b * 7 +$vPx.a * 11
EndFunc
#EndRegion INTERNAL
Alles anzeigen
Minimalbeispiel gibts im Anhang.
Falls Fehler vorhanden sind, gerne hier posten bzw. Leute mit PU/Mod Rechten können für fixes auch einfach meinen Beitrag editieren ![]()
Spoiler anzeigen
Edit: Habe mir noch ein paar Gedanken gemacht:
Problem 1: QOI nutzt keine vertikale Ähnlichkeit von Pixeln aus.
Potenzielle Lösung 1: Sortierte Quadtree Koordinaten (eine schnelle Methode wären z.B. Morton-Codes) statt Pixel mit x y Koordinaten.
Erwartete Verbesserung: 5% - 30%
Problem 2: QOI encodete Bilder sind nicht redundanzfrei und nicht musterfrei
Potenzielle Lösung 2: Nachgeschaltetes (mini) LZSS + Nachgeschalteter (mini) Entropieencoder (mit "mini" meine ich Varianten die keine Zusatzdaten speichern, also keinen Header haben)
Erwartete Verbesserung: 5% - 30%
Wenn man beides macht schätze ich, dass man png in Bezug zur Kompressionsrate locker besiegen kann. (das wäre dann ohnehin sehr ähnlich zu png, wobei man darauf hofft, dass QOI hier im ersten Schritt besser performed als die Filter von png)
lg
M