Класс UImage: получение информации об объекте изображения
Получить информацию об объекте изображения позволяют следующие методы::
get_width() и get_height() — возвращают ширину и высоту соответственно. Пример:
from unicross_image.ucolor import UColor
from unicross_image.uimage import UImage
img = UImage(300, 200, UColor(255, 0, 0, 128))
w = img.get_width()
h = img.get_height()
print(w, h) # 300 200
get_size() — возвращает объект USize с размерами изображения. Пример:
s = img.get_size()
print(s) # USize(width=300, height=200)
w = s.get_width()
h = s.get_height()
print(w, h) # 300 200
w, h = img.get_size()
print(w, h) # 300 200
get_rect() — возвращает объект URect с координатами и размерами изображения. Пример:
r = img.get_rect()
print(r) # URect(x=0, y=0, width=300, height=200)
x = r.get_x()
y = r.get_y()
w = r.get_width()
h = r.get_height()
print(x, y, w, h) # 0 0 300 200
x, y, w, h = img.get_rect()
print(x, y, w, h) # 0 0 300 200
get_num_channels() — возвращает число каналов. Пример:
print(img.get_num_channels()) # 4
get_img_mode() — возвращает режим изображения в виде строки. Пример:
print(img.get_img_mode()) # RGBA
get_mat_type() — возвращает тип матрицы (значение UMat.Type_8U). Пример:
print(img.get_mat_type()) # 1
get_arr_len() — возвращает количество элементов списка arr, в котором хранится изображение. При этом вычисление размера списка не производится. Размер списка вычисляется только при создании объекта изображения и при вызове метода update_arr_len(). Если объект не является валидным, то значение, возвращаемое методом, будет неправильным. Пример:
print(img.get_arr_len()) # 240000
print(len(img.arr)) # 240000
update_arr_len() — обновляет информацию о размере списка с изображением. Ничего не возвращает;
is_valid() — возвращает значение True, если объект изображения является валидным, и False — в противном случае. Проверяется соответствие размеров изображения и числа каналов размеру списка. Содержимое списка не проверяется. Пример:
img = UImage(300, 200, UColor(255, 0, 0, 128))
print(img.is_valid()) # True
img2 = UImage(3, 2, create_arr=False)
print(img2.arr) # []
print(img2.get_arr_len()) # 0
img2.arr = [255] * 24 # Добавляем существующий список
print(img2.is_valid()) # False
img2.update_arr_len() # Обновление данных
print(img2.is_valid()) # True
print(img2.get_arr_len()) # 24
is_transparent() — возвращает значение True, если объект изображения содержит прозрачные пиксели, и False — в противном случае. Пример:
img = UImage(300, 200, UColor(255, 0, 0, 128))
print(img.is_transparent()) # True
img2 = UImage(3, 2, UColor(255, 0, 0, 255))
print(img2.is_transparent()) # False
Получение и изменение пикселей изображения
Получить или изменить значений пикселей изображения позволяют следующие методы:
fill(<UColor>) — заливает все изображение заданным цветом. В качестве параметра указывается объект UColor. В результате выполнения операции создается новый список, который заменит старый список. Метод возвращает значение True, если операция выполнена успешно, и False — в противном случае. Пример:
from unicross_image.ucolor import UColor
from unicross_image.uimage import UImage
img = UImage(3, 2)
print(img.fill(UColor(255, 0, 0, 128))) # True
print(img.arr)
# [255, 0, 0, 128, 255, 0, 0, 128, 255, 0, 0, 128,
# 255, 0, 0, 128, 255, 0, 0, 128, 255, 0, 0, 128]
get_pixel(x, y) — возвращает кортеж из 4-х элементов (r, g, b, a) для пикселя, расположенного по координатам x и y. Валидность объекта не проверяется. Если координаты находятся вне пределов изображения, то метод вернет значение None. Пример:
print(img.get_pixel(0, 0)) # (255, 0, 0, 128)
print(img.get_pixel(10, -1)) # None
get_pixel_ucolor(x, y) — возвращает объект UColor с характеристиками цвета для пикселя, расположенного по координатам x и y. Валидность объекта не проверяется. Если координаты находятся вне пределов изображения, то метод вернет значение None. Пример:
print(img.get_pixel_ucolor(0, 0))
# UColor(red=255, green=0, blue=0, opacity=128)
print(img.get_pixel_ucolor(10, -1))
# None
set_pixel(x, y, r, g, b, a) — изменяет цвет пикселя, расположенного по координатам x и y. Валидность объекта и диапазоны значений для компонентов цвета не проверяются. Если операция выполнена успешно, то метод вернет значение True. Если координаты находятся вне пределов изображения, то метод вернет значение False. Пример:
print(img.set_pixel(0, 0, 128, 255, 0, 255)) # True
print(img.get_pixel(0, 0)) # (128, 255, 0, 255)
print(img.set_pixel(10, -1, 0, 0, 0, 255)) # False
# Диапазоны значений не проверяются!
print(img.set_pixel(0, 0, -1, 256, 300, 0)) # True
print(img.get_pixel(0, 0)) # (-1, 256, 300, 0)
set_pixel_ucolor(x, y, <UColor>) — изменяет цвет пикселя, расположенного по координатам x и y. Характеристики цвета задаются с помощью объекта UColor. Валидность объекта не проверяется. Если операция выполнена успешно, то метод вернет значение True. Если координаты находятся вне пределов изображения, то метод вернет значение False. Пример:
print(img.set_pixel_ucolor(0, 0, UColor(128, 255, 0, 255)))
# True
print(img.get_pixel(0, 0))
# (128, 255, 0, 255)
print(img.set_pixel_ucolor(10, -1, UColor(0, 0, 0, 255)))
# False
В основе объекта изображения лежит одномерный список, доступный через атрибут arr. Размер списка вычисляется следующим образом:
arr_len = width * height * num_channels
Строки двумерной матрицы записываются в список слева направо и сверху вниз. Каждый пиксель описывается 4-я элементами в последовательности RGBA. Давайте зальем все изображение прозрачным цветом с помощью метода set_pixel() и измерим время выполнения:
import time
from unicross_image.uimage import UImage
img = UImage(3000, 2000)
d_start = time.time()
r, g, b, a = 0, 0, 0, 0
for y in range(img.get_height()):
for x in range(img.get_width()):
img.set_pixel(x, y, r, g, b, a)
print(time.time() - d_start) # 8.520143985748291
Этот код с синтаксической точки зрения абсолютно верный. Однако он содержит огромную проблему. Код абсолютно неэффективный! Если мы заменим метод set_pixel() методом set_pixel_ucolor(), то эффективность еще уменьшится в несколько раз. Проблема кроется в огромном количестве лишних действий и проверок. Посмотрите на результат. Время выполнения составляет почти 9 секунд. Катастрофа!
Чтобы повысить эффективность, нужно обращаться к пикселям напрямую через атрибут arr. При этом обращаться к списку следует не через объект изображения, а через ссылку, предварительно сохраненную в переменной. Вычисление положения элемента внутри списка arr, при известных координатах x и y, производится по следующей формуле:
i = x * num_channels + y * width * num_channels
Переделаем наш предыдущий пример:
import time
from unicross_image.uimage import UImage
img = UImage(3000, 2000)
d_start = time.time()
r, g, b, a = 0, 0, 0, 0
img_width = img.get_width()
img_height = img.get_height()
img_num_channels = img.get_num_channels()
img_line = img_width * img_num_channels
img_arr = img.arr
for y in range(img_height):
for x in range(img_height):
i = x * img_num_channels + y * img_line
img_arr[i] = r
img_arr[i + 1] = g
img_arr[i + 2] = b
img_arr[i + 3] = a
print(time.time() - d_start) # 2.805180549621582
Время выполнения сократилось почти в три раза! Учитывая, что положение пикселя по осям X и Y в этой задаче нам не нужно, будем работать с одномерным списком:
import time
from unicross_image.uimage import UImage
img = UImage(3000, 2000)
d_start = time.time()
r, g, b, a = 0, 0, 0, 0
img_arr_len = img.get_arr_len()
img_num_channels = img.get_num_channels()
img_arr = img.arr
for i in range(0, img_arr_len, img_num_channels):
img_arr[i] = r
img_arr[i + 1] = g
img_arr[i + 2] = b
img_arr[i + 3] = a
print(time.time() - d_start) # 2.6563830375671387
Выиграли еще несколько десятых секунды. Мы можем лучше! В нашей задаче одно значение должно заполнить весь список. Для этого перебирать все значения списка не нужно, достаточно создать список заново средствами языка Python:
import time
from unicross_image.uimage import UImage
img = UImage(3000, 2000)
d_start = time.time()
img.arr = [0] * img.get_arr_len()
print(time.time() - d_start) # 0.18751168251037598
print(img.is_valid()) # True
Ну вот, наша задача оказывается решается за две десятых секунды, вместо девяти секунд! Так что, используйте список напрямую и выбирайте способ выполнения в зависимости от задачи!
Заполнить список произвольными значениями можно так:
from unicross_image.uimage import UImage
img = UImage(3, 2)
img.arr = [255, 128, 0, 255] * (img.get_width() * img.get_height())
print(img.is_valid()) # True
print(img.arr)
# [255, 128, 0, 255, 255, 128, 0, 255, 255, 128, 0, 255,
# 255, 128, 0, 255, 255, 128, 0, 255, 255, 128, 0, 255]
Метод fill() выполняет аналогичное действие, поэтому код можно записать так:
from unicross_image.uimage import UImage
from unicross_image.ucolor import UColor
img = UImage(3, 2)
img.fill(UColor(255, 128, 0, 255))
print(img.is_valid()) # True
print(img.arr)
# [255, 128, 0, 255, 255, 128, 0, 255, 255, 128, 0, 255,
# 255, 128, 0, 255, 255, 128, 0, 255, 255, 128, 0, 255]
Нормализация диапазона значений
Значения внутри списка arr должны находиться в диапазоне от 0 до 255. При выполнении различных операций можно выйти за границы этого диапазона. Учитывая, что список в Python может хранить весь диапазон целых чисел, увеличение максимального значения на единицу приведет лишь к числу 256. Если подобную операцию выполнить при использовании массива NumPy с типом uint8, то мы получим число 0, т. е. белый цвет превратится в черный. В Python такой проблемы нет.
Если внутри списка существует хотя бы одно значение вне диапазона от 0 до 255, то сохранить такое изображение или преобразовать его в другой объект не получится. Проверить валидность объекта и целостность диапазона значений позволяет метод is_normal(). Метод возвращает значение True, если объект валидный и список можно преобразовать в массив байтов, и False — в противном случае. Пример:
from unicross_image.uimage import UImage
from unicross_image.ucolor import UColor
img = UImage(3, 2, UColor(255, 128, 0, 255))
print(img.is_normal()) # True
img.arr[0] = 256
img.arr[1] = -1
print(img.arr[:4]) # [256, -1, 0, 255]
print(img.is_normal()) # False
Вернуть значения внутрь диапазона позволяет метод normalize(). Если значение меньше 0, то оно будет заменено значением 0, если значение больше 255, то оно будет заменено значением 255. Метод возвращает значение True, если объект валидный и список можно преобразовать в массив байтов, и False — в противном случае. Пример:
print(img.normalize()) # True
print(img.arr[:4]) # [255, 0, 0, 255]
print(img.is_normal()) # True
Создание копии изображения
Создать копию изображения позволяет метод copy(). Если операция выполнена успешно, то метод вернет копию объекта, а в противном случае — значение None. Пример:
from unicross_image.uimage import UImage
from unicross_image.ucolor import UColor
img = UImage(3, 2, UColor(255, 128, 0, 255))
print(img.arr[:4]) # [255, 128, 0, 255]
img2 = img.copy()
print(img2) # UImage(width=3, height=2)
print(img2 is img) # False
img2.arr[0] = 0
print(img2.arr[:4]) # [0, 128, 0, 255]
print(img.arr[:4]) # [255, 128, 0, 255]
Если объект изображения не является валидным, то метод вернет значение None:
img3 = UImage(3, 2, create_arr=False)
img4 = img3.copy()
print(img4) # None
Если просто присвоить объект другой переменной, то будет скопирована лишь ссылка на объект, а не сам объект:
img2 = img
print(img2) # UImage(width=3, height=2)
print(img2 is img) # True
img2.arr[0] = 0
print(img2.arr[:4]) # [0, 128, 0, 255]
print(img.arr[:4]) # [0, 128, 0, 255]
Как видно из результата, изменение списка через один объект, привело к изменению списка в другом объекте. Иными словами, оба объекта ссылаются на один и тот же список.
Сравнение изображений
Сравнить два объекта изображений можно с помощью операторов == и !=.Пример:
from unicross_image.uimage import UImage
from unicross_image.ucolor import UColor
img = UImage(3, 2, UColor(255, 128, 0, 255))
img2 = UImage(3, 2, UColor(255, 128, 0, 255))
img3 = UImage(3, 2, UColor(255, 255, 255, 255))
print(img == img2) # True
print(img == img3) # False
print(img != img2) # False
print(img != img3) # True
Для проверки, ссылаются ли две переменные на один и тот же объект, нужно использовать оператор is:
img4 = img
print(img is img4) # True
print(img is img2) # False
Класс входит в состав графической библиотеки UImage для Python 3. Описание библиотеки UImage