# otherwise
#
-import bpy, math, gpu
+import bpy, math, gpu, os
import cProfile
from ctypes import *
from mathutils import *
("material_id",c_uint32)] # index into the material array
#}
+class mdl_texture(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("pstr_name",c_uint32),
+ ("pack_offset",c_uint32),
+ ("pack_length",c_uint32)]
+#}
+
class mdl_material(Structure):
#{
_pack_ = 1
- _fields_ = [("pstr_name",c_uint32)]
+ _fields_ = [("pstr_name",c_uint32),
+ ("shader",c_uint32),
+ ("flags",c_uint32),
+ ("surface_prop",c_uint32),
+ ("colour",c_float*4),
+ ("colour1",c_float*4),
+ ("tex_diffuse",c_uint32),
+ ("tex_decal",c_uint32),
+ ("tex_normal",c_uint32)]
#}
class mdl_node(Structure):
("material_count",c_uint32),
("material_offset",c_uint32),
+ ("texture_count",c_uint32),
+ ("texture_offset",c_uint32),
+
("anim_count",c_uint32),
("anim_offset",c_uint32),
("vertex_offset",c_uint32),
("indice_count",c_uint32),
- ("indice_offset",c_uint32),]
+ ("indice_offset",c_uint32),
+
+ ("pack_size",c_uint32),
+ ("pack_offset",c_uint32)]
#}
class mdl_animation(Structure):
indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
for l in indices:
+ #{
v0 = vs[l[0]]
v1 = vs[l[1]]
cv_view_verts += [(v0[0],v0[1],v0[2])]
cv_view_verts += [(v1[0],v1[1],v1[2])]
cv_view_colours += [(1,1,0,1),(1,1,0,1)]
+ #}
sw = (0.4,0.4,0.4,0.2)
if obj.cv_data.target != None:
cv_view_verts += [(v1[0],v1[1],v1[2])]
cv_view_colours += [(0,1,1,1),(0,1,1,1)]
#}
+
+ cv_draw_sphere( obj.location, 20.0, [0.1,0,0.9,0.4] )
#}
@staticmethod
#}
#}
+class classtype_spawn_link(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("connections",c_uint32*4)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 0
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ pass
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ count = 0
+
+ for obj1 in bpy.context.collection.objects:
+ #{
+ if (obj1.cv_data.classtype != 'classtype_spawn_link') and \
+ (obj1.cv_data.classtype != 'classtype_spawn') :
+ continue
+
+ if (obj1.location - obj.location).length < 40.0:
+ #{
+ cv_draw_line( obj.location, obj1.location, [1,1,1,1] )
+ count +=1
+ #}
+
+ if count == 4:
+ break
+ #}
+
+ cv_draw_sphere( obj.location, 20.0, [0.5,0,0.2,0.4] )
+ #}
+#}
+
# ---------------------------------------------------------------------------- #
# #
# Compiler section #
# Reset encoder
#
-def encoder_init():
+def encoder_init( collection ):
#{
global g_encoder
#
'header': mdl_header(),
+ # Options
+ #
+ 'pack_textures': collection.cv_data.pack_textures,
+
# Compiled data chunks (each can be read optionally by the client)
#
'data':
'node': [], # Metadata 'chunk'
'submesh': [],
'material': [],
+ 'texture': [],
'anim': [],
'entdata': bytearray(), # variable width
'strings': bytearray(), # .
#3---------------------------------
'vertex': [], # Mesh data
'indice': [],
+ #4---------------------------------
+ 'pack': bytearray() # Other generic packed data
},
# All objects of the model in their final heirachy
'string_cache':{},
'mesh_cache': {},
'material_cache': {},
+ 'texture_cache': {}
}
g_encoder['header'].identifier = 0xABCD0000
g_encoder['header'].version = 1
- # Add fake NoneID material
+ # Add fake NoneID material and texture
#
- none_material = c_uint32(1234)
- none_material.name = ""
- encoder_process_material( none_material )
+ none_material = mdl_material()
+ none_material.pstr_name = encoder_process_pstr( "" )
+ none_material.texture_id = 0
+
+ none_texture = mdl_texture()
+ none_texture.pstr_name = encoder_process_pstr( "" )
+ none_texture.pack_offset = 0
+ none_texture.pack_length = 0
+
+ g_encoder['data']['material'] += [none_material]
+ g_encoder['data']['texture'] += [none_texture]
+
+ g_encoder['data']['pack'].extend( b'datapack\0\0\0\0\0\0\0\0' )
# Add root node
#
return cache[s]
#}
+def get_texture_resource_name( img ):
+#{
+ return os.path.splitext( img.name )[0]
+#}
+
+# Pack a texture
+#
+def encoder_process_texture( img ):
+#{
+ global g_encoder
+
+ if img == None:
+ return 0
+
+ cache = g_encoder['texture_cache']
+ buffer = g_encoder['data']['texture']
+ pack = g_encoder['data']['pack']
+
+ name = get_texture_resource_name( img )
+
+ if name in cache:
+ return cache[name]
+
+ cache[name] = len( buffer )
+
+ tex = mdl_texture()
+ tex.pstr_name = encoder_process_pstr( name )
+
+ if g_encoder['pack_textures']:
+ #{
+ tex.pack_offset = len( pack )
+ pack.extend( qoi_encode( img ) )
+ tex.pack_length = len( pack ) - tex.pack_offset
+ #}
+ else:
+ tex.pack_offset = 0
+
+ buffer += [ tex ]
+ return cache[name]
+#}
+
+def material_tex_image(v):
+#{
+ return {
+ "Image Texture":
+ {
+ "image": F"{v}"
+ }
+ }
+#}
+
+cxr_graph_mapping = \
+{
+ # Default shader setup
+ "Principled BSDF":
+ {
+ "Base Color":
+ {
+ "Image Texture":
+ {
+ "image": "tex_diffuse"
+ },
+ "Mix":
+ {
+ "Color1": material_tex_image("tex_diffuse"),
+ "Color2": material_tex_image("tex_decal")
+ },
+ },
+ "Normal":
+ {
+ "Normal Map":
+ {
+ "Color": material_tex_image("tex_normal")
+ }
+ }
+ }
+}
+
+# https://harrygodden.com/git/?p=convexer.git;a=blob;f=__init__.py;#l1164
+#
+def material_info(mat):
+#{
+ info = {}
+
+ # Using the cv_graph_mapping as a reference, go through the shader
+ # graph and gather all $props from it.
+ #
+ def _graph_read( node_def, node=None, depth=0 ):
+ #{
+ nonlocal mat
+ nonlocal info
+
+ # Find rootnodes
+ #
+ if node == None:
+ #{
+ _graph_read.extracted = []
+
+ 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
+ break
+ #}
+ #}
+ #}
+ #}
+
+ for link in node_def:
+ #{
+ link_def = node_def[link]
+
+ if isinstance( link_def, dict ):
+ #{
+ node_link = node.inputs[link]
+
+ if node_link.is_linked:
+ #{
+ # look for definitions for the connected node type
+ #
+ from_node = node_link.links[0].from_node
+
+ node_name = from_node.name.split('.')[0]
+ if node_name in link_def:
+ #{
+ from_node_def = link_def[ node_name ]
+
+ _graph_read( from_node_def, from_node, depth+1 )
+ #}
+
+ # No definition! :(
+ # TODO: Make a warning for this?
+ #}
+ else:
+ #{
+ if "default" in link_def:
+ #{
+ prop = link_def['default']
+ info[prop] = node_link.default_value
+ #}
+ #}
+ #}
+ else:
+ #{
+ prop = link_def
+ info[prop] = getattr( node, link )
+ #}
+ #}
+ #}
+
+ _graph_read( cxr_graph_mapping )
+ return info
+#}
+
# Add a material to the material buffer. Returns 0 (None ID) if invalid
#
def encoder_process_material( mat ):
dest = mdl_material()
dest.pstr_name = encoder_process_pstr( mat.name )
- buffer += [dest]
+
+ flags = 0x00
+ if mat.cv_data.skate_surface: flags |= 0x1
+ if mat.cv_data.collision: flags |= 0x2
+ if mat.cv_data.grow_grass: flags |= 0x4
+ dest.flags = flags
+
+ if mat.cv_data.surface_prop == 'concrete': dest.surface_prop = 0
+ if mat.cv_data.surface_prop == 'wood': dest.surface_prop = 1
+ if mat.cv_data.surface_prop == 'grass': dest.surface_prop = 2
+
+ if mat.cv_data.shader == 'standard': dest.shader = 0
+ if mat.cv_data.shader == 'standard_cutout': dest.shader = 1
+ if mat.cv_data.shader == 'terrain_blend':
+ #{
+ dest.shader = 2
+
+ dest.colour[0] = pow( mat.cv_data.sand_colour[0], 1.0/2.2 )
+ dest.colour[1] = pow( mat.cv_data.sand_colour[1], 1.0/2.2 )
+ dest.colour[2] = pow( mat.cv_data.sand_colour[2], 1.0/2.2 )
+ dest.colour[3] = 1.0
+
+ dest.colour1[0] = mat.cv_data.blend_offset[0]
+ dest.colour1[1] = mat.cv_data.blend_offset[1]
+ #}
+ if mat.cv_data.shader == 'vertex_blend':
+ #{
+ dest.shader = 3
+
+ dest.colour1[0] = mat.cv_data.blend_offset[0]
+ dest.colour1[1] = mat.cv_data.blend_offset[1]
+ #}
+
+ if mat.cv_data.shader == 'water':
+ #{
+ dest.shader = 4
+
+ dest.colour[0] = pow( mat.cv_data.shore_colour[0], 1.0/2.2 )
+ dest.colour[1] = pow( mat.cv_data.shore_colour[1], 1.0/2.2 )
+ dest.colour[2] = pow( mat.cv_data.shore_colour[2], 1.0/2.2 )
+ dest.colour[3] = 1.0
+ dest.colour1[0] = pow( mat.cv_data.ocean_colour[0], 1.0/2.2 )
+ dest.colour1[1] = pow( mat.cv_data.ocean_colour[1], 1.0/2.2 )
+ dest.colour1[2] = pow( mat.cv_data.ocean_colour[2], 1.0/2.2 )
+ dest.colour1[3] = 1.0
+ #}
+
+ inf = material_info( mat )
+
+ if mat.cv_data.shader == 'standard' or \
+ mat.cv_data.shader == 'standard_cutout' or \
+ mat.cv_data.shader == 'terrain_blend' or \
+ mat.cv_data.shader == 'vertex_blend':
+ #{
+ if 'tex_diffuse' in inf:
+ dest.tex_diffuse = encoder_process_texture(inf['tex_diffuse'])
+ #}
+
+ buffer += [dest]
return cache[mat.name]
#}
node.submesh_start = len( g_encoder['data']['submesh'] )
node.submesh_count = 0
- default_mat = c_uint32(12345)
- default_mat.name = ""
-
dgraph = bpy.context.evaluated_depsgraph_get()
data = obj.evaluated_get(dgraph).data
data.calc_loop_triangles()
# Mesh is split into submeshes based on their material
#
- mat_list = data.materials if len(data.materials) > 0 else [default_mat]
+ mat_list = data.materials if len(data.materials) > 0 else [None]
for material_id, mat in enumerate(mat_list):
#{
mref = {}
#{
global g_encoder
print( F"Model graph | Create mode '{collection_name}'" )
+ folder = bpy.path.abspath(bpy.context.scene.cv_data.export_dir)
+ path = F"{folder}{collection_name}.mdl"
+ print( path )
collection = bpy.data.collections[collection_name]
- encoder_init()
+ encoder_init( collection )
encoder_build_scene_graph( collection )
# Compile
# Write
#
- # TODO HOLY
- path = F"/home/harry/Documents/carve/models_src/{collection_name}.mdl"
encoder_write_to_file( path )
print( F"Completed {collection_name}.mdl" )
('classtype_trigger',"classtype_trigger","",100),
('classtype_logic_achievement',"classtype_logic_achievement","",101),
('classtype_logic_relay',"classtype_logic_relay","",102),
+ ('classtype_spawn_link',"classtype_spawn_link","",150),
])
#}
class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
#{
use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
+ export_dir: bpy.props.StringProperty( name="Export Dir", subtype='DIR_PATH' )
+#}
+
+class CV_COLLECTION_SETTINGS(bpy.types.PropertyGroup):
+#{
+ pack_textures: bpy.props.BoolProperty( name="Pack Textures", default=False )
+#}
+
+class CV_MATERIAL_SETTINGS(bpy.types.PropertyGroup):
+#{
+ shader: bpy.props.EnumProperty(
+ name="Format",
+ items = [
+ ('standard',"standard","",0),
+ ('standard_cutout', "standard_cutout", "", 1),
+ ('terrain_blend', "terrain_blend", "", 2),
+ ('vertex_blend', "vertex_blend", "", 3),
+ ('water',"water","",4),
+ ])
+
+ surface_prop: bpy.props.EnumProperty(
+ name="Surface Property",
+ items = [
+ ('concrete','concrete','',0),
+ ('wood','wood','',1),
+ ('grass','grass','',2)
+ ])
+
+ collision: bpy.props.BoolProperty( \
+ name="Collisions Enabled",\
+ default=True,\
+ description = "Can the player collide with this material"\
+ )
+ skate_surface: bpy.props.BoolProperty( \
+ name="Skate Surface", \
+ default=True,\
+ description = "Should the game try to target this surface?" \
+ )
+ grow_grass: bpy.props.BoolProperty( \
+ name="Grow Grass", \
+ default=False,\
+ description = "Spawn grass sprites on this surface?" \
+ )
+ blend_offset: bpy.props.FloatVectorProperty( \
+ name="Blend Offset", \
+ size=2, \
+ default=Vector((0.5,0.0)),\
+ description="When surface is more than 45 degrees, add this vector " +\
+ "to the UVs" \
+ )
+ sand_colour: bpy.props.FloatVectorProperty( \
+ name="Sand Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.79,0.63,0.48)),\
+ description="Blend to this colour near the 0 coordinate on UP axis"\
+ )
+ shore_colour: bpy.props.FloatVectorProperty( \
+ name="Shore Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.03,0.32,0.61)),\
+ description="Water colour at the shoreline"\
+ )
+ ocean_colour: bpy.props.FloatVectorProperty( \
+ name="Ocean Colour",\
+ subtype='COLOR',\
+ min=0.0,max=1.0,\
+ default=Vector((0.0,0.006,0.03)),\
+ description="Water colour in the deep bits"\
+ )
+#}
+
+class CV_MATERIAL_PANEL(bpy.types.Panel):
+#{
+ bl_label="Skate Rift material"
+ bl_idname="MATERIAL_PT_cv_material"
+ bl_space_type='PROPERTIES'
+ bl_region_type='WINDOW'
+ bl_context="material"
+
+ def draw(_,context):
+ #{
+ active_object = bpy.context.active_object
+ if active_object == None: return
+ active_mat = active_object.active_material
+ if active_mat == None: return
+
+ info = material_info( active_mat )
+
+ _.layout.prop( active_mat.cv_data, "shader" )
+ _.layout.prop( active_mat.cv_data, "surface_prop" )
+ _.layout.prop( active_mat.cv_data, "collision" )
+
+ if active_mat.cv_data.collision:
+ _.layout.prop( active_mat.cv_data, "skate_surface" )
+ _.layout.prop( active_mat.cv_data, "grow_grass" )
+
+ if active_mat.cv_data.shader == "terrain_blend":
+ #{
+ box = _.layout.box()
+ box.prop( active_mat.cv_data, "blend_offset" )
+ box.prop( active_mat.cv_data, "sand_colour" )
+ #}
+ elif active_mat.cv_data.shader == "vertex_blend":
+ #{
+ box = _.layout.box()
+ box.label( icon='INFO', text="Uses vertex colours, the R channel" )
+ box.prop( active_mat.cv_data, "blend_offset" )
+ #}
+ elif active_mat.cv_data.shader == "water":
+ #{
+ box = _.layout.box()
+ box.label( icon='INFO', text="Depth scale of 16 meters" )
+ box.prop( active_mat.cv_data, "shore_colour" )
+ box.prop( active_mat.cv_data, "ocean_colour" )
+ #}
+ #}
#}
class CV_OBJ_PANEL(bpy.types.Panel):
#}
#}
-class CV_INTERFACE(bpy.types.Panel):
+class CV_COMPILE(bpy.types.Operator):
#{
- bl_idname = "VIEW3D_PT_carve"
- bl_label = "Carve"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = "Carve"
+ bl_idname="carve.compile_all"
+ bl_label="Compile All"
- def draw(_, context):
+ def execute(_,context):
#{
- layout = _.layout
- layout.prop( context.scene.cv_data, "use_hidden")
- layout.operator( "carve.compile_all" )
+ view_layer = bpy.context.view_layer
+ for col in view_layer.layer_collection.children["export"].children:
+ if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
+ write_model( col.name )
+
+ return {'FINISHED'}
#}
#}
-def test_compile():
+class CV_COMPILE_THIS(bpy.types.Operator):
#{
- view_layer = bpy.context.view_layer
- for col in view_layer.layer_collection.children["export"].children:
- if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
- write_model( col.name )
+ bl_idname="carve.compile_this"
+ bl_label="Compile This collection"
+
+ def execute(_,context):
+ #{
+ col = bpy.context.collection
+ write_model( col.name )
+
+ return {'FINISHED'}
+ #}
#}
-class CV_COMPILE(bpy.types.Operator):
+class CV_INTERFACE(bpy.types.Panel):
#{
- bl_idname="carve.compile_all"
- bl_label="Compile All"
+ bl_idname = "VIEW3D_PT_carve"
+ bl_label = "Skate Rift"
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "Skate Rift"
- def execute(_,context):
+ def draw(_, context):
#{
- test_compile()
- #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
- #for col in bpy.data.collections["export"].children:
- # write_model( col.name )
+ layout = _.layout
+ layout.prop( context.scene.cv_data, "export_dir" )
+
+ col = bpy.context.collection
+
+ found_in_export = False
+ export_count = 0
+ view_layer = bpy.context.view_layer
+ for c1 in view_layer.layer_collection.children["export"].children:
+ #{
+ if not c1.hide_viewport or bpy.context.scene.cv_data.use_hidden:
+ export_count += 1
- return {'FINISHED'}
+ if c1.name == col.name:
+ #{
+ found_in_export = True
+ #}
+ #}
+
+ box = layout.box()
+ if found_in_export:
+ #{
+ box.label( text=col.name + ".mdl" )
+ box.prop( col.cv_data, "pack_textures" )
+ box.operator( "carve.compile_this" )
+ #}
+ else:
+ #{
+ row = box.row()
+ row.enabled=False
+ row.label( text=col.name )
+ box.label( text="This collection is not in the export group" )
+ #}
+
+ box = layout.box()
+ row = box.row()
+
+ split = row.split( factor = 0.3, align=True )
+ split.prop( context.scene.cv_data, "use_hidden", text="hidden" )
+
+ row1 = split.row()
+ if export_count == 0:
+ row1.enabled=False
+ row1.operator( "carve.compile_all", \
+ text=F"Compile all ({export_count} collections)" )
#}
#}
+
classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
- CV_BONE_PANEL]
+ CV_BONE_PANEL, CV_COLLECTION_SETTINGS, CV_COMPILE_THIS,\
+ CV_MATERIAL_SETTINGS, CV_MATERIAL_PANEL ]
def register():
#{
bpy.types.Mesh.cv_data = bpy.props.PointerProperty(type=CV_MESH_SETTINGS)
bpy.types.Scene.cv_data = bpy.props.PointerProperty(type=CV_SCENE_SETTINGS)
bpy.types.Bone.cv_data = bpy.props.PointerProperty(type=CV_BONE_SETTINGS)
+ bpy.types.Collection.cv_data = \
+ bpy.props.PointerProperty(type=CV_COLLECTION_SETTINGS)
+ bpy.types.Material.cv_data = \
+ bpy.props.PointerProperty(type=CV_MATERIAL_SETTINGS)
cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
cv_draw,(),'WINDOW','POST_VIEW')
bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_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) ])
+#}
+
+def qoi_encode( img ):
+#{
+ data = bytearray()
+
+ print(F" . Encoding {img.name}.qoi[{img.size[0]},{img.size[1]}]")
+
+ 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, 0 )
+
+ return data
+#}