UImage C API
Графическая библиотека UImageC содержит код на языке C, который можно вызвать из программ на Python, C или C++. Код компилируется в динамические библиотеки (dll) под Windows x64. Подключить библиотеку в программе на Python позволяет модуль ctypes, входящий в состав стандартной библиотеки. Пример подключения библиотеки libuimagegray.dll, расположенной в папке bin, относительно местоположения исполняемого файла:
import os.path
import ctypes
_ulibpath = os.path.abspath(
os.path.join(os.path.dirname(__file__), "bin", "libuimagegray.dll"))
_ulibc = ctypes.CDLL(_ulibpath)
Следует учитывать, что при неправильном использовании функций из C API возможны непредвиденные последствия. Это касается всего языка C в целом. Например, функция принимает указатель на массив. Внутри функции нет никакой возможности определить число элементов массива. Оператор sizeof() вернет лишь размер указателя, а не размер всего массива. Число элементов массива обычно передается дополнительным параметром, но нет никакой гарантии, что это число соответствует реальному размеру массива. Поэтому контроль за правильностью передаваемых в функцию данных лежит на плечах программиста.
Знакомство с массивами ctypes
Пиксели изображения в библиотеке UImageC хранятся в массивах ctypes, что позволяет получить к ним доступ из программы на языке C без необходимости выполнения преобразований. В отличие от списков языка Python массивы ctypes являются строго типизированными и имеют фиксированный размер.
Типы данных в модуле ctypes соответствуют типам из языка C. Перечислим основные типы, которые часто встречаются в библиотеке UImageC:
>>> import ctypes
>>> b = ctypes.c_bool(True) # _Bool
>>> ctypes.sizeof(b)
1
>>> n = ctypes.c_ubyte(5) # unsigned char
>>> ctypes.sizeof(n)
1
>>> n = ctypes.c_int(5) # int или long
>>> ctypes.sizeof(n)
4
>>> n = ctypes.c_long(5) # long
>>> ctypes.sizeof(n)
4
>>> n = ctypes.c_float(5.2) # float
>>> ctypes.sizeof(n)
4
>>> n = ctypes.c_double(5.2) # double
>>> ctypes.sizeof(n)
8
>>> type(n)
<class 'ctypes.c_double'>
>>> n.value
5.2
Создать указатель позволяет функция pointer():
>>> n = ctypes.c_int(5)
>>> pn = ctypes.pointer(n)
>>> pn.contents
c_long(5)
>>> pn[0] = 10
>>> n
c_long(10)
Существует также функция POINTER(), которая используется для объявления типизированных указателей. Например, для прототипа:
int uimagergb_invert(unsigned char* parr, int arr_len);
код описания параметров выглядит так:
_uimagergb_invert = _ulibc.uimagergb_invert
_uimagergb_invert.argtypes = [
ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int]
_uimagergb_invert.restype = ctypes.c_int
Объявление указателя на C-строку (заканчивается нулевым символом) выполняется с помощью типа c_char_p. Например, для прототипа:
int uimagegray_to_bwstr(int* parr_int, int arr_len_int,
char* s, int s_len);
код описания параметров выглядит так:
_uimagegray_to_bwstr = _ulibc.uimagegray_to_bwstr
_uimagegray_to_bwstr.argtypes = [
ctypes.POINTER(ctypes.c_int), ctypes.c_int,
ctypes.c_char_p, ctypes.c_int]
_uimagegray_to_bwstr.restype = ctypes.c_int
Строки в языке Python неизменяемые. Для создания изменяемого строкового буфера предназначена функция create_string_buffer():
>>> s = b'Hello'
>>> ps = ctypes.create_string_buffer(s)
>>> ps.raw
b'Hello\x00'
>>> ps.value
b'Hello'
>>> type(ps)
<class 'ctypes.c_char_Array_6'>
>>> ctypes.sizeof(ps)
6
>>> ps[0] = b'_'
>>> ps.value
b'_ello'
>>> ps = ctypes.create_string_buffer(3)
>>> ps.raw
b'\x00\x00\x00'
Для создания типизированного массива нужно умножить тип на число элементов, а затем вызвать конструктор:
>>> arr = (ctypes.c_int * 5)()
>>> arr[:]
[0, 0, 0, 0, 0]
>>> arr = (ctypes.c_int * 5)(0, 1, 2, 3, 4)
>>> arr[:]
[0, 1, 2, 3, 4]
>>> arr[0]
0
>>> arr[0] = 55
>>> arr[:]
[55, 1, 2, 3, 4]
Создать массив на основе списка можно так:
>>> arr = [0, 1, 2, 3, 4]
>>> type(arr)
<class 'list'>
>>> arr2 = (ctypes.c_int * len(arr))(*arr)
>>> arr2[:]
[0, 1, 2, 3, 4]
>>> type(arr2)
<class '__main__.c_long_Array_5'>
Массивы являются экземплярами класса ctypes.Array. Проверить тип и получить информацию о массиве позволяет следующий код:
>>> arr = (ctypes.c_int * 5)(0, 1, 2, 3, 4)
>>> isinstance(arr, ctypes.Array)
True
>>> arr._length_
5
>>> arr._type_
<class 'ctypes.c_long'>
>>> ctypes.sizeof(arr)
20
Для преобразования массива в список можно использовать операцию извлечения среза или функцию list():
>>> arr = (ctypes.c_int * 5)(0, 1, 2, 3, 4)
>>> arr2 = arr[:]
>>> type(arr2)
<class 'list'>
>>> arr3 = list(arr)
>>> type(arr3)
<class 'list'>
Класс UCArray: работа с массивами ctypes
Класс UCArray содержит методы для работы с массивами ctypes. Инструкция импорта:
from unicross_img.uhelper import UCArray
Создание массива ctypes
Создать массив ctypes с типом c_int (c_long) позволяет статический метод create_ctypes_array_int() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_int(width, height, num_channels)
В первом параметре указывается ширина изображения от 1 до 8000. Второй параметр задает высоту изображения от 1 до 8000. Параметр num_channels определяет число каналов изображения (1, 3 или 4). Метод возвращает массив ctypes заполненный нулями. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
arr = UCArray.create_ctypes_array_int(3, 2, 1)
print(arr._length_) # 6
print(arr._type_) # <class 'ctypes.c_long'>
print(arr[:])
# [0, 0, 0,
# 0, 0, 0]
arr = UCArray.create_ctypes_array_int(3, 2, 3)
print(arr._length_) # 18
print(arr[:])
# [0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0]
arr = UCArray.create_ctypes_array_int(3, 2, 4)
print(arr._length_) # 24
print(arr[:])
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Создать массив ctypes с типом c_ubyte (соответствует типу unsigned char в языке C) позволяет статический метод create_ctypes_array_ubyte() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_ubyte(width, height, num_channels)
В первом параметре указывается ширина изображения от 1 до 8000. Второй параметр задает высоту изображения от 1 до 8000. Параметр num_channels определяет число каналов изображения (1, 3 или 4). Метод возвращает массив ctypes заполненный нулями. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
arr = UCArray.create_ctypes_array_ubyte(3, 2, 1)
print(arr._length_) # 6
print(arr._type_) # <class 'ctypes.c_ubyte'>
print(arr[:])
# [0, 0, 0,
# 0, 0, 0]
arr = UCArray.create_ctypes_array_ubyte(3, 2, 3)
print(arr._length_) # 18
print(arr[:])
# [0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0]
arr = UCArray.create_ctypes_array_ubyte(3, 2, 4)
print(arr._length_) # 24
print(arr[:])
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Создать массив ctypes с типом c_double позволяет статический метод create_ctypes_array_double() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_double(width, height, num_channels)
В первом параметре указывается ширина изображения от 1 до 8000. Второй параметр задает высоту изображения от 1 до 8000. Параметр num_channels определяет число каналов изображения (1, 3 или 4). Метод возвращает массив ctypes заполненный нулями. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
arr = UCArray.create_ctypes_array_double(3, 2, 1)
print(arr._length_) # 6
print(arr._type_) # <class 'ctypes.c_double'>
print(arr[:])
# [0.0, 0.0, 0.0,
# 0.0, 0.0, 0.0]
arr = UCArray.create_ctypes_array_double(3, 2, 3)
print(arr._length_) # 18
print(arr[:])
# [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
# 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
arr = UCArray.create_ctypes_array_double(3, 2, 4)
print(arr._length_) # 24
print(arr[:])
# [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
# 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
Создать массив ctypes с типом c_bool позволяет статический метод create_ctypes_array_bool() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_bool(width, height)
В первом параметре указывается ширина изображения от 1 до 8000. Второй параметр задает высоту изображения от 1 до 8000. Метод возвращает массив ctypes заполненный значениями False. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
arr = UCArray.create_ctypes_array_bool(3, 2)
print(arr._length_) # 6
print(arr._type_) # <class 'ctypes.c_bool'>
print(arr[:])
# [False, False, False,
# False, False, False]
Создание массива ctypes на основе списка
Создать массив ctypes с типом c_int (c_long) на основе списка с целыми числами позволяет статический метод create_ctypes_array_int_from_list() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_int_from_list(<Список>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
list_int = [0, 64, 128, 200, 255]
arr = UCArray.create_ctypes_array_int_from_list(list_int)
print(arr._length_) # 5
print(arr._type_) # <class 'ctypes.c_long'>
print(arr[:]) # [0, 64, 128, 200, 255]
Создать массив ctypes с типом c_double на основе списка с вещественными числами позволяет статический метод create_ctypes_array_double_from_list() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_double_from_list(<Список>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
list_d = [0.0, 0.5, 1.0]
arr = UCArray.create_ctypes_array_double_from_list(list_d)
print(arr._length_) # 3
print(arr._type_) # <class 'ctypes.c_double'>
print(arr[:]) # [0.0, 0.5, 1.0]
Создать массив ctypes с типом c_bool на основе списка с логическими значениями позволяет статический метод create_ctypes_array_bool_from_list() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_bool_from_list(<Список>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
list_b = [True, False, True]
arr = UCArray.create_ctypes_array_bool_from_list(list_b)
print(arr._length_) # 3
print(arr._type_) # <class 'ctypes.c_bool'>
print(arr[:]) # [True, False, True]
Создание массива ctypes на основе объекта типа bytes
Создать массив ctypes с типом c_ubyte на основе объекта типа bytes или bytearray позволяет статический метод create_ctypes_array_ubyte_from_bytes() из класса UCArray. Формат метода:
UCArray.create_ctypes_array_ubyte_from_bytes(<bytes>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
from unicross_img.uhelper import UCArray
b = bytes([0, 1, 2, 3, 4])
print(type(b)) # <class 'bytes'>
arr = UCArray.create_ctypes_array_ubyte_from_bytes(b)
print(arr._length_) # 5
print(arr._type_) # <class 'ctypes.c_ubyte'>
print(arr[:]) # [0, 1, 2, 3, 4]
b = bytearray(b)
print(type(b)) # <class 'bytearray'>
arr = UCArray.create_ctypes_array_ubyte_from_bytes(b)
print(arr._length_) # 5
print(arr._type_) # <class 'ctypes.c_ubyte'>
print(arr[:]) # [0, 1, 2, 3, 4]
Создание копии массива ctypes
Создать копию массива ctypes с типом c_ubyte позволяет статический метод copy_ctypes_array_ubyte() из класса UCArray. Формат метода:
UCArray.copy_ctypes_array_ubyte(<Массив ctypes>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
import ctypes
from unicross_img.uhelper import UCArray
arr = (ctypes.c_ubyte * 5)(0, 1, 2, 3, 4)
arr2 = UCArray.copy_ctypes_array_ubyte(arr)
print(arr2._length_) # 5
print(arr2._type_) # <class 'ctypes.c_ubyte'>
arr2[0] = 55
print(arr2[:]) # [55, 1, 2, 3, 4]
print(arr[:]) # [0, 1, 2, 3, 4]
Создать копию массива ctypes произвольного типа позволяет статический метод copy_ctypes_array() из класса UCArray. Формат метода:
UCArray.copy_ctypes_array(<Массив ctypes>)
Метод возвращает массив ctypes. При ошибке генерируется исключение. Пример:
import ctypes
from unicross_img.uhelper import UCArray
arr = (ctypes.c_int * 5)(0, 1, 2, 3, 4)
arr2 = UCArray.copy_ctypes_array(arr)
print(arr2._length_) # 5
print(arr2._type_) # <class 'ctypes.c_long'>
arr2[0] = 55
print(arr2[:]) # [55, 1, 2, 3, 4]
print(arr[:]) # [0, 1, 2, 3, 4]
arr = (ctypes.c_double * 3)(0.0, 0.5, 1.0)
arr2 = UCArray.copy_ctypes_array(arr)
print(arr2._length_) # 3
print(arr2._type_) # <class 'ctypes.c_double'>
arr2[0] = 1.0
print(arr2[:]) # [1.0, 0.5, 1.0]
print(arr[:]) # [0.0, 0.5, 1.0]
Класс входит в состав графической библиотеки UImageC. Описание библиотеки UImageC