move texture compiler to native code
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index b49bf48553a6886d58ab57dc7dc3876aba21d699..19e68f0b64c66cc37d8e87bb16d7e1214c129a1e 100644 (file)
@@ -1,5 +1,6 @@
-import bpy, blf, math, gpu, os
+import bpy, blf, math, gpu, os, time
 import cProfile
+import numpy as np
 from ctypes import *
 from mathutils import *
 from gpu_extras.batch import batch_for_shader
@@ -43,13 +44,16 @@ sr_entity_list = [
    ('ent_miniworld',    'Mini World',     '', 22 ),
    ('ent_prop',         'Prop',           '', 23 ),
    ('ent_list',         'Entity List',    '', 24 ),
-   ('ent_region',       'Region',         '', 25 )
+   ('ent_region',       'Region',         '', 25 ),
+   ('ent_glider',       'Glider',         '', 26 ),
+   ('ent_npc',          'npc',            '', 27 )
 ]
 
-MDL_VERSION_NR = 104
+MDL_VERSION_NR = 105
 SR_TRIGGERABLE = [ 'ent_audio', 'ent_ccmd', 'ent_gate', 'ent_challenge', \
                    'ent_relay', 'ent_skateshop', 'ent_objective', 'ent_route',\
-                   'ent_miniworld', 'ent_region' ]
+                   'ent_miniworld', 'ent_region', 'ent_glider', 'ent_list',\
+                   'ent_npc' ]
 
 def get_entity_enum_id( alias ):
 #{
@@ -244,12 +248,26 @@ class union_file_audio_clip(Union):
                ("reserved",vg_audio_clip)]
 #}
 
+# NOTE: not really an entity. no reason for ent_ -- makes more sense as file_,
+#       but then again, too late to change because compat.
 class ent_audio_clip(Structure):
 #{
    _fields_ = [("_anon",union_file_audio_clip),
                ("probability",c_float)]
 #}
 
+class ent_list(Structure):
+#{
+   _fields_ = [("entity_ref_start",c_uint32),
+               ("entity_ref_count",c_uint32)]
+#}
+
+# used in ent_list
+class file_entity_ref(Structure):
+#{
+   _fields_ = [("index",c_uint32)]
+#}
+
 class ent_checkpoint(Structure):
 #{
    _fields_ = [("gate_index",c_uint16),
@@ -279,6 +297,22 @@ class ent_list(Structure):#{
    _fields_ = [("start",c_uint16),("count",c_uint16)]
 #}
 
+class ent_glider(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("flags",c_uint32),
+               ("cooldown",c_float)]
+   sr_functions = { 0: 'unlock',
+                    1: 'equip' }
+#}
+
+class ent_npc(Structure):#{
+   _fields_ = [("transform",mdl_transform),
+               ("id",c_uint32),
+               ("context",c_uint32),
+               ("camera",c_uint32)]
+   sr_functions = { 0: 'proximity', -1: 'leave' }
+#}
+
 class ent_water(Structure):
 #{
    _fields_ = [("transform",mdl_transform),
@@ -290,7 +324,7 @@ class ent_water(Structure):
 class volume_trigger(Structure):
 #{
    _fields_ = [("event",c_uint32),
-               ("event_leave",c_uint32)]
+               ("event_leave",c_int32)]
 #}
 
 class volume_particles(Structure):
@@ -484,7 +518,8 @@ class ent_worldinfo(Structure):
                ("pstr_author",c_uint32),    # unused
                ("pstr_desc",c_uint32),      # unused
                ("timezone",c_float),
-               ("pstr_skybox",c_uint32)]
+               ("pstr_skybox",c_uint32),
+               ("flags",c_uint32)]
 #}
 
 class ent_ccmd(Structure):
@@ -499,7 +534,7 @@ class ent_objective(Structure):#{
                ("id_next",c_uint32),
                ("filter",c_uint32),("filter2",c_uint32),
                ("id_win",c_uint32),
-               ("win_event",c_uint32),
+               ("win_event",c_int32),
                ("time_limit",c_float)]
 
    sr_functions = { 0: 'trigger',
@@ -512,9 +547,9 @@ class ent_challenge(Structure):#{
                ("pstr_alias",c_uint32),
                ("flags",c_uint32),
                ("target",c_uint32),
-               ("target_event",c_uint32),
+               ("target_event",c_int32),
                ("reset",c_uint32),
-               ("reset_event",c_uint32),
+               ("reset_event",c_int32),
                ("first",c_uint32),
                ("camera",c_uint32),
                ("status",c_uint32)] #runtime
@@ -527,13 +562,15 @@ class ent_region(Structure):#{
                ("submesh_start",c_uint32), ("submesh_count",c_uint32),
                ("pstr_title",c_uint32),
                ("flags",c_uint32),
-               ("zone_volume",c_uint32)]
+               ("zone_volume",c_uint32),
+               #105+
+               ("target0",c_uint32*2)]
    sr_functions = { 0: 'enter', 1: 'leave' }
 #}
 
 class ent_relay(Structure):#{
    _fields_ = [("targets",(c_uint32*2)*4),
-               ("targets_events",c_uint32*4)]
+               ("targets_events",c_int32*4)]
    sr_functions = { 0: 'trigger' }
 #}
 
@@ -547,6 +584,8 @@ class ent_cubemap(Structure):#{
                ("placeholder",c_uint32*2)]
 #}
 
+print( sizeof(ent_cubemap) )
+
 class ent_miniworld(Structure):#{
    _fields_ = [("transform",mdl_transform),
                ("pstr_world",c_uint32),
@@ -560,7 +599,8 @@ class ent_prop(Structure):#{
    _fields_ = [("transform",mdl_transform),
                ("submesh_start",c_uint32),
                ("submesh_count",c_uint32),
-               ("flags",c_uint32)]
+               ("flags",c_uint32),
+               ("pstr_alias",c_uint32)]
 #}
 
 def obj_ent_type( obj ):
@@ -733,14 +773,17 @@ def material_info(mat):
       if node == None:#{
          _graph_read.extracted = []
 
+         done = False
          for node_idname in node_def:#{
             for n in mat.node_tree.nodes:#{
                if n.name == node_idname:#{
                   node_def = node_def[node_idname]
                   node = n
+                  done = True
                   break
                #}
             #}
+            if done: break
          #}
       #}
 
@@ -868,6 +911,7 @@ def sr_compile_material( mat ):#{
 
    if mat.SR_data.shader == 'standard': m.shader = 0
    if mat.SR_data.shader == 'standard_cutout': m.shader = 1
+   if mat.SR_data.shader == 'foliage': m.shader = 10
    if mat.SR_data.shader == 'terrain_blend':#{
       m.shader = 2
 
@@ -927,7 +971,8 @@ def sr_compile_material( mat ):#{
    #}
    
    if mat.SR_data.shader in ['standard', 'standard_cutout', 'terrain_blend', \
-                             'vertex_blend', 'fxglow', 'cubemap' ]: #{
+                             'vertex_blend', 'fxglow', 'cubemap', \
+                             'foliage' ]: #{
       if 'tex_diffuse' in inf: 
          m.tex_diffuse = sr_compile_texture(inf['tex_diffuse'])
    #}
@@ -1714,12 +1759,13 @@ def sr_compile( collection ):
          #}
          #--------------------------
 
-         print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}', end='\r' )
+         print( F'[SR] {i: 3}/{mesh_count} {obj.name:<40}' )
          sr_compile_mesh( obj )
       #}
    #}
 
    audio_clip_count = 0
+   entity_file_ref_count = 0
 
    for ent_type, arr in sr_compile.entities.items():#{
       print(F"[SR] Compiling {len(arr)} {ent_type}{'s' if len(arr)>1 else ''}")
@@ -1878,10 +1924,7 @@ def sr_compile( collection ):
             if obj_data.target:#{
                volume.target = sr_entity_id( obj_data.target )
                volume._anon.trigger.event = obj_data.target_event
-
-               ev = 0xffffffff if obj_data.target_event_leave < 0 else \
-                     obj_data.target_event_leave
-               volume._anon.trigger.event_leave = ev
+               volume._anon.trigger.event_leave = obj_data.target_event_leave
             #}
 
             sr_ent_push(volume)
@@ -1934,7 +1977,17 @@ def sr_compile( collection ):
             worldinfo.pstr_name = sr_compile_string( obj_data.name )
             worldinfo.pstr_author = sr_compile_string( obj_data.author )
             worldinfo.pstr_desc = sr_compile_string( obj_data.desc )
-            worldinfo.timezone = obj_data.timezone
+
+            flags = 0x00
+
+            if obj_data.fix_time:#{
+               worldinfo.timezone = obj_data.fixed_time
+               flags |= 0x1
+            #}
+            else:
+               worldinfo.timezone = obj_data.timezone
+
+            worldinfo.flags = flags
             worldinfo.pstr_skybox = sr_compile_string( obj_data.skybox )
             sr_ent_push( worldinfo )
          #}
@@ -1984,6 +2037,8 @@ def sr_compile( collection ):
                   sr_compile_mesh_internal( obj )
             region.pstr_title = sr_compile_string( obj_data.title )
             region.zone_volume = sr_entity_id( obj_data.zone_volume )
+            region.target0[0] = sr_entity_id( obj_data.target0 )
+            region.target0[1] = obj_data.target0_event
             sr_ent_push( region )
          #}
          elif ent_type == 'ent_relay':#{
@@ -1999,6 +2054,36 @@ def sr_compile( collection ):
             relay.targets[3][1] = obj_data.target3_event
             sr_ent_push( relay )
          #}
+         # elif ent_type == 'ent_list':#{
+         #    lista = ent_list()
+         #    obj_data = obj.SR_data.ent_list[0]
+
+         #    lista.entity_ref_start = entity_file_ref_count
+         #    lista.entity_ref_count = len( obj_data.entities )
+         #    entity_file_ref_count += lista.entity_ref_count
+
+         #    for child in obj_data.entities:#{
+         #       reference_struct = file_entity_ref()
+         #       reference_struct.index = sr_entity_id( child.target )
+         #       sr_ent_push( reference_struct )
+         #    #}
+
+         #    sr_ent_push( lista )
+         # #}
+         elif ent_type == 'ent_glider':#{
+            glider = ent_glider()
+            compile_obj_transform( obj, glider.transform )
+            sr_ent_push( glider )
+         #}
+         elif ent_type == 'ent_npc':#{
+            obj_data = obj.SR_data.ent_npc[0]
+            npc = ent_npc()
+            compile_obj_transform( obj, npc.transform )
+            npc.id = obj_data.au
+            npc.context = obj_data.context
+            npc.camera = sr_entity_id( obj_data.cam )
+            sr_ent_push( npc )
+         #}
          elif ent_type == 'ent_cubemap':#{
             cubemap = ent_cubemap()
             co = obj.matrix_world @ Vector((0,0,0))
@@ -2021,10 +2106,12 @@ def sr_compile( collection ):
          #}
          elif ent_type == 'ent_prop':#{
             prop = ent_prop()
+            obj_data = obj.SR_data.ent_prop[0]
             compile_obj_transform( obj, prop.transform )
             prop.submesh_start, prop.submesh_count, _ = \
                   sr_compile_mesh_internal( obj )
-            prop.flags = 0
+            prop.flags = obj_data.flags
+            prop.pstr_alias = sr_compile_string( obj_data.alias )
             sr_ent_push( prop )
          #}
       #}
@@ -2543,7 +2630,6 @@ class SR_MATERIAL_PANEL(bpy.types.Panel):
       _.layout.prop( active_mat.SR_data, "shader" )
       _.layout.prop( active_mat.SR_data, "surface_prop" )
       _.layout.prop( active_mat.SR_data, "collision" )
-      _.layout.prop( active_mat.SR_data, "tex_diffuse_rt" )
 
       if active_mat.SR_data.collision:#{
          box = _.layout.box()
@@ -2580,6 +2666,10 @@ class SR_MATERIAL_PANEL(bpy.types.Panel):
          box.prop( active_mat.SR_data, "cubemap" )
          box.prop( active_mat.SR_data, "tint" )
       #}
+
+      _.layout.label( text="" )
+      _.layout.label( text="advanced (you probably don't want to edit these)" )
+      _.layout.prop( active_mat.SR_data, "tex_diffuse_rt" )
    #}
 #}
 
@@ -3071,6 +3161,17 @@ class SR_OBJECT_ENT_LIST(bpy.types.PropertyGroup):#{
    #}
 #}
 
+class SR_OBJECT_ENT_GLIDER(bpy.types.PropertyGroup):#{
+   nothing: bpy.props.StringProperty()
+#}
+
+class SR_OBJECT_ENT_NPC(bpy.types.PropertyGroup):#{
+   au: bpy.props.IntProperty()
+   context: bpy.props.IntProperty()
+   cam: bpy.props.PointerProperty( \
+           type=bpy.types.Object, name="Viewpoint", \
+           poll=lambda self,obj: sr_filter_ent_type(obj,['ent_camera']))
+#}
 
 class SR_OBJECT_ENT_VOLUME(bpy.types.PropertyGroup):#{
    subtype: bpy.props.EnumProperty(
@@ -3202,6 +3303,7 @@ class SR_OBJECT_ENT_AUDIO(bpy.types.PropertyGroup):
 class SR_OBJECT_ENT_MARKER(bpy.types.PropertyGroup):
 #{
    alias: bpy.props.StringProperty()
+   flags: bpy.props.IntProperty()
 #}
 
 class SR_OBJECT_ENT_GLYPH(bpy.types.PropertyGroup):
@@ -3409,8 +3511,24 @@ class SR_OBJECT_ENT_WORLD_INFO(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty(name="Name")
    desc: bpy.props.StringProperty(name="Description")
    author: bpy.props.StringProperty(name="Author")
-   timezone: bpy.props.FloatProperty(name="Timezone(hrs) (UTC0 +hrs)")
    skybox: bpy.props.StringProperty(name="Skybox")
+
+   fix_time: bpy.props.BoolProperty(name="Fix Time")
+   timezone: bpy.props.FloatProperty(name="Timezone(hrs) (UTC0 +hrs)")
+   fixed_time: bpy.props.FloatProperty(name="Fixed Time (0-1)")
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'name' )
+      layout.prop( data[0], 'desc' )
+      layout.prop( data[0], 'author' )
+
+      layout.prop( data[0], 'fix_time' )
+      if data[0].fix_time:
+         layout.prop( data[0], 'fixed_time' )
+      else:
+         layout.prop( data[0], 'timezone' )
+   #}
 #}
 
 class SR_OBJECT_ENT_CCMD(bpy.types.PropertyGroup):
@@ -3491,6 +3609,18 @@ class SR_OBJECT_ENT_REGION(bpy.types.PropertyGroup):#{
    zone_volume: bpy.props.PointerProperty(
             type=bpy.types.Object, name="Zone Volume", \
             poll=lambda self,obj: sr_filter_ent_type(obj,['ent_volume']))
+
+   target0: bpy.props.PointerProperty( \
+            type=bpy.types.Object, name="Triger on unlock", \
+            poll=lambda self,obj: sr_filter_ent_type(obj,SR_TRIGGERABLE))
+   target0_event: bpy.props.IntProperty( name="Event/Method" )
+
+   @staticmethod
+   def sr_inspector( layout, data ):#{
+      layout.prop( data[0], 'title' )
+      layout.prop( data[0], 'zone_volume' )
+      SR_OBJECT_ENT_VOLUME.inspect_target( layout, data, 'target0' )
+   #}
 #}
 
 class SR_OBJECT_ENT_RELAY(bpy.types.PropertyGroup):#{
@@ -3529,6 +3659,7 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
    ent_volume: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_VOLUME)
    ent_audio: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_AUDIO)
    ent_marker: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
+   ent_prop: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MARKER)
    ent_glyph: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLYPH)
    ent_font: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_FONT)
    ent_traffic: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_TRAFFIC)
@@ -3544,6 +3675,8 @@ class SR_OBJECT_PROPERTIES(bpy.types.PropertyGroup):
    ent_relay: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_RELAY)
    ent_miniworld: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_MINIWORLD)
    ent_list: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_LIST)
+   ent_glider: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_GLIDER)
+   ent_npc: bpy.props.CollectionProperty(type=SR_OBJECT_ENT_NPC)
 
    ent_type: bpy.props.EnumProperty(
       name="Type",
@@ -3621,7 +3754,8 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
       ('boundary','Boundary',''),
       ('fxglow','FX Glow',''),
       ('cubemap','Cubemap',''),
-      ('walking','Walking','')
+      ('walking','Walking',''),
+      ('foliage','Foliage','')
       ])
 
    surface_prop: bpy.props.EnumProperty(
@@ -3631,7 +3765,9 @@ class SR_MATERIAL_PROPERTIES(bpy.types.PropertyGroup):
       ('1','wood',''),
       ('2','grass',''),
       ('3','tiles',''),
-      ('4','metal','')
+      ('4','metal',''),
+      ('5','snow (low friction)',''),
+      ('6','sand (medium friction)','')
       ])
    
    collision: bpy.props.BoolProperty( \
@@ -4165,6 +4301,14 @@ def draw_skeleton_helpers( obj ):
    #}
 #}
 
+def cv_draw_wireframe( mdl, points, colour ):#{
+   for i in range(len(points)//2):#{
+      p0 = mdl@points[i*2+0]
+      p1 = mdl@points[i*2+1]
+      cv_draw_line( p0, p1, colour )
+   #}
+#}
+
 def cv_ent_gate( obj ):
 #{
    global cv_view_verts, cv_view_colours
@@ -4579,6 +4723,20 @@ def cv_draw():#{
                #}
             #}
          #}
+         elif ent_type == 'ent_glider':#{
+            mesh = [Vector((-1.13982, 0.137084, -0.026358)), \
+                    Vector(( 1.13982, 0.137084, -0.026358)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector(( 0.0, -3.0, 1.0)), \
+                    Vector(( -3.45, -1.78, 0.9)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector((  3.45, -1.78, 0.9)), \
+                    Vector(( 0.0, 1.6, 1.0)), \
+                    Vector((  3.45, -1.78, 0.9)), \
+                    Vector(( -3.45, -1.78, 0.9))]
+
+            cv_draw_wireframe( obj.matrix_world, mesh, (1,1,1) )
+         #}
          elif ent_type == 'ent_skateshop':#{
             data = obj.SR_data.ent_skateshop[0]
             display = data.mark_display
@@ -4646,6 +4804,22 @@ def cv_draw():#{
             if display1:
                cv_draw_ucube(display1.matrix_world, cc1, display_cu, display_co)
          #}
+         # elif ent_type == 'ent_list':#{
+         #    data = obj.SR_data.ent_list[0]
+         #    for child in data.entities:#{
+         #       if child.target:#{
+         #          cv_draw_arrow( obj.location, child.target.location, \
+         #                         (.5,.5,.5), 0.1 )
+         #       #}
+         #    #}
+         # #}
+         elif ent_type == 'ent_region':#{
+            data = obj.SR_data.ent_region[0]
+            if data.target0:#{
+               cv_draw_arrow( obj.location, data.target0.location, \
+                              (.5,.5,.5), 0.1 )
+            #}
+         #}
          elif ent_type == 'ent_menuitem':#{
             for i,col in enumerate(obj.users_collection):#{
                colour32 = hash_djb2( col.name )
@@ -4782,6 +4956,7 @@ classes = [ SR_INTERFACE, SR_MATERIAL_PANEL,\
             SR_OBJECT_ENT_RELAY,SR_OBJECT_ENT_MINIWORLD,\
             SR_OBJECT_ENT_LIST_ENTRY, SR_UL_ENT_LIST, SR_OBJECT_ENT_LIST, \
             SR_OT_ENT_LIST_NEW_ITEM, SR_OT_ENT_LIST_DEL_ITEM,\
+            SR_OBJECT_ENT_GLIDER, SR_OBJECT_ENT_NPC, \
             \
             SR_OBJECT_PROPERTIES, SR_LIGHT_PROPERTIES, SR_BONE_PROPERTIES, 
             SR_MESH_PROPERTIES, SR_MATERIAL_PROPERTIES \
@@ -4825,176 +5000,43 @@ def unregister():
    bpy.types.SpaceView3D.draw_handler_remove(cv_view_pixel_handler,'WINDOW')
 #}
 
-# ---------------------------------------------------------------------------- #
-#                                                                              #
-#                                 QOI encoder                                  #
-#                                                                              #
-# ---------------------------------------------------------------------------- #
-#                                                                              #
-# Transliteration of:                                                          #
-#    https://github.com/phoboslab/qoi/blob/master/qoi.h                        #
-#                                                                              #
-# Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org               #
-# SPDX-License-Identifier: MIT                                                 #
-# QOI - The "Quite OK Image" format for fast, lossless image compression       #
-#                                                                              #
-# ---------------------------------------------------------------------------- #
-
-class qoi_rgba_t(Structure):
-#{
-   _pack_ = 1
-   _fields_ = [("r",c_uint8),
-               ("g",c_uint8),
-               ("b",c_uint8),
-               ("a",c_uint8)]
-#}
-
-QOI_OP_INDEX  = 0x00 # 00xxxxxx
-QOI_OP_DIFF   = 0x40 # 01xxxxxx
-QOI_OP_LUMA   = 0x80 # 10xxxxxx
-QOI_OP_RUN    = 0xc0 # 11xxxxxx
-QOI_OP_RGB    = 0xfe # 11111110
-QOI_OP_RGBA   = 0xff # 11111111
-
-QOI_MASK_2    = 0xc0 # 11000000
-
-def qoi_colour_hash( c ):
-#{
-   return c.r*3 + c.g*5 + c.b*7 + c.a*11
-#}
-
-def qoi_eq( a, b ):
-#{
-   return (a.r==b.r) and (a.g==b.g) and (a.b==b.b) and (a.a==b.a)
-#}
-
-def qoi_32bit( v ):
-#{
-   return bytearray([ (0xff000000 & v) >> 24, \
-                      (0x00ff0000 & v) >> 16, \
-                      (0x0000ff00 & v) >> 8, \
-                      (0x000000ff & v) ])
-#}
+qoi_lib = None
+qoi_encode_rgbaf32 = None
+qoi_free = None
 
 def qoi_encode( img ):
 #{
-   data = bytearray()
+   global qoi_lib
+   global qoi_encode_rgbaf32
+   global qoi_free
    
+   if not qoi_lib:
+   #{
+      ext = '.dll' if os.name=='nt' else '.so'
+      path = F'{os.path.dirname(__file__)}/qoi{ext}'
+      qoi_lib = cdll.LoadLibrary( path )
+      qoi_encode_rgbaf32 = qoi_lib.qoi_encode_rgbaf32
+      qoi_encode_rgbaf32.argtypes = \
+         [ np.ctypeslib.ndpointer(dtype=np.float32,\
+                                  ndim=1,\
+                                  flags='C_CONTIGUOUS'), \
+          c_uint32, c_uint32, POINTER(c_int32) ]
+      qoi_encode_rgbaf32.restype = POINTER(c_uint8)
+
+      qoi_free = qoi_lib.qoi_free
+      qoi_free.argtypes = [ POINTER(c_uint8) ]
+   #}
+
    print(F"{' ':<30}",end='\r')
    print(F"[QOI] Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]",end='\r')
 
-   index = [ qoi_rgba_t() for _ in range(64) ]
-
-   # Header
-   #
-   data.extend( bytearray(c_uint32(0x66696f71)) )
-   data.extend( qoi_32bit( img.size[0] ) )
-   data.extend( qoi_32bit( img.size[1] ) )
-   data.extend( bytearray(c_uint8(4)) )
-   data.extend( bytearray(c_uint8(0)) )
-
-   run = 0
-   px_prev = qoi_rgba_t()
-   px_prev.r = c_uint8(0)
-   px_prev.g = c_uint8(0)
-   px_prev.b = c_uint8(0)
-   px_prev.a = c_uint8(255)
-
-   px = qoi_rgba_t()
-   px.r = c_uint8(0)
-   px.g = c_uint8(0)
-   px.b = c_uint8(0)
-   px.a = c_uint8(255)
-
-   px_len = img.size[0] * img.size[1]
-   paxels = [ int(min(max(_,0),1)*255) for _ in img.pixels ]
-
-   for px_pos in range( px_len ): #{
-      idx = px_pos * img.channels
-      nc = img.channels-1
-
-      px.r = paxels[idx+min(0,nc)]
-      px.g = paxels[idx+min(1,nc)]
-      px.b = paxels[idx+min(2,nc)]
-      px.a = paxels[idx+min(3,nc)]
-
-      if qoi_eq( px, px_prev ): #{
-         run += 1
-
-         if (run == 62) or (px_pos == px_len-1): #{
-            data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
-            run = 0
-         #}
-      #}
-      else: #{
-         if run > 0: #{
-            data.extend( bytearray( c_uint8(QOI_OP_RUN | (run-1))) )
-            run = 0
-         #}
-
-         index_pos = qoi_colour_hash(px) % 64
-
-         if qoi_eq( index[index_pos], px ): #{
-            data.extend( bytearray( c_uint8(QOI_OP_INDEX | index_pos)) )
-         #}
-         else: #{
-            index[ index_pos ].r = px.r
-            index[ index_pos ].g = px.g
-            index[ index_pos ].b = px.b
-            index[ index_pos ].a = px.a
-
-            if px.a == px_prev.a: #{
-               vr = int(px.r) - int(px_prev.r)
-               vg = int(px.g) - int(px_prev.g)
-               vb = int(px.b) - int(px_prev.b)
-
-               vg_r = vr - vg
-               vg_b = vb - vg
-
-               if (vr > -3) and (vr < 2) and\
-                  (vg > -3) and (vg < 2) and\
-                  (vb > -3) and (vb < 2):
-               #{
-                  op = QOI_OP_DIFF | (vr+2) << 4 | (vg+2) << 2 | (vb+2)
-                  data.extend( bytearray( c_uint8(op) ))
-               #}
-               elif (vg_r > -9) and (vg_r < 8) and\
-                    (vg  > -33) and (vg < 32 ) and\
-                    (vg_b > -9) and (vg_b < 8):
-               #{
-                  op = QOI_OP_LUMA | (vg+32)
-                  delta = (vg_r+8) << 4 | (vg_b + 8)
-                  data.extend( bytearray( c_uint8(op) ) )
-                  data.extend( bytearray( c_uint8(delta) ))
-               #}
-               else: #{
-                  data.extend( bytearray( c_uint8(QOI_OP_RGB) ) )
-                  data.extend( bytearray( c_uint8(px.r) ))
-                  data.extend( bytearray( c_uint8(px.g) ))
-                  data.extend( bytearray( c_uint8(px.b) ))
-               #}
-            #}
-            else: #{
-               data.extend( bytearray( c_uint8(QOI_OP_RGBA) ) )
-               data.extend( bytearray( c_uint8(px.r) ))
-               data.extend( bytearray( c_uint8(px.g) ))
-               data.extend( bytearray( c_uint8(px.b) ))
-               data.extend( bytearray( c_uint8(px.a) ))
-            #}
-         #}
-      #}
-
-      px_prev.r = px.r
-      px_prev.g = px.g
-      px_prev.b = px.b
-      px_prev.a = px.a
-   #}
-   
-   # Padding
-   for i in range(7):
-      data.extend( bytearray( c_uint8(0) ))
-   data.extend( bytearray( c_uint8(1) ))
-   bytearray_align_to( data, 16, b'\x00' )
+   crab = np.asarray(img.pixels, dtype=np.float32)
+   cock = c_int()
+   comped = qoi_encode_rgbaf32( crab, img.size[0], img.size[1], byref(cock) )
+   end = time.time()
 
-   return data
+   bingo = bytearray(comped[:cock.value])
+   bytearray_align_to( bingo, 16, b'\x00' )
+   qoi_free( comped )
+   return bingo
 #}