Есть ли простой способ повернуть импортированное изображение на холсте tkinter? Я бы предпочел не использовать модуль PIL, но я не могу найти жизнеспособных альтернатив. (Если это поможет, я хочу повернуть некоторые изображения автомобилей, когда они поворачивают на перекрестке.)
Как повернуть изображение на холсте без использования PIL?
Ответы (3)
Ниже приведен простой, но неэффективный метод поворота PhotoImage
на 90 (вправо), 180 и 270 (влево) градусов:
def rotate_image(img, dir):
w, h = img.width(), img.height()
if dir in ['left', 'right']:
newimg = PhotoImage(width=h, height=w)
else: # 180 degree
newimg = PhotoImage(width=w, height=h)
for x in range(w):
for y in range(h):
rgb = '#%02x%02x%02x' % img.get(x, y)
if dir == 'right': # 90 degrees
newimg.put(rgb, (h-y,x))
elif dir == 'left': # -90 or 270 degrees
newimg.put(rgb, (y,w-x))
else: # 180 degrees
newimg.put(rgb, (w-x,h-y))
return newimg
Большое спасибо за ответ @acw1668 на этой странице, который помог мне разработать это более эффективное решение.
Оказывается, метод PhotoImage.put() принимает строковые данные и записывает любой шаблон, который вы ему даете, в виде строки в цикле, чтобы заполнить заданную область изображения. Таким образом, вместо того, чтобы читать каждый пиксель один за другим, а затем записывать каждый пиксель один за другим, мы можем прочитать каждый пиксель, а затем записать только один раз!
Приведенная ниже функция позволяет вам вращать и отражать любой объект PhotoImage и делает это за долю времени, которое делает метод чтения-записи «пиксель в пиксель». Зеркальное отображение выполняется простым чтением каждой строки или столбца с конца наперед. Вращение записывает каждую строку как столбец, что эффективно поворачивает изображение на 90 градусов.
import tkinter as tk
def putToImage(brush, canvas, bbox, mirror_x=False, mirror_y=False, rotate=False):
value1 = brush.height() if rotate else brush.width()
value2 = brush.width() if rotate else brush.height()
start1, end1, increment1 = (value1 - 1, -1, -1) if mirror_x else (0, value1, 1)
start2, end2, increment2 = (value2 - 1, -1, -1) if mirror_y else (0, value2, 1)
data = ""
for col in range(start2, end2, increment2):
data = data + "{"
for row in range(start1, end1, increment1):
data = data + "#%02x%02x%02x " % brush.get(col if rotate else row, row if rotate else col)
data = data + "} "
canvas.put(data, to=bbox)
А вот простой пример использования:
window = tk.Tk()
lbl1 = tk.Label(window)
lbl2 = tk.Label(window)
my_img = tk.PhotoImage(file="my_image.png")
rotated_img = tk.PhotoImage(width=my_img.height(), height=my_img.width())
putToImage(my_img, rotated_img, (0, 0, rotated_img.width(), rotated_img.height()), rotate=True)
lbl1.configure(image=my_img)
lbl1.pack()
lbl2.configure(image=rotated_img)
lbl2.pack()
window.mainloop()
Вот способ поворота изображения с помощью tkinter без модуля PIL.
Итак, вот еще несколько объяснений, прежде чем я покажу свой ответ.
Во-первых, координаты холста имеют отрицательную ось Y, поэтому вам нужно быть осторожным с этим, когда думаете о математике. Я решил перейти на «стандартную» систему. Потом позже конвертирую обратно. Что бы вы ни делали, просто будьте осторожны, потому что легко запутаться.
Here is a basic outline:
- Создайте новое пустое изображение, возможно, оно должно быть больше, чтобы вместить повернутое изображение.
- Проходи пиксель за пикселем и получай цвет исходного изображения.
- Поместите цвет в пиксели нового изображения, но в новую область на новом изображении.
Here is a more in-depth outline on how to rotate the image (I choose to change the axis to the more 'standard' math coordinates where positive y values are up, not down like a lot of computer stuff.)
- измените ось y на «стандартные» математические координаты. Это означает, что изображение будет находиться в квадранте 4 в декартовой системе координат (x положительное, y отрицательное)
- сдвиньте местоположение изображения так, чтобы начало координат находилось в середине изображения - теперь изображение расположено во всех 4 квадрантах с (0,0) в середине изображения
- примените стандартную формулу поворота вокруг начала координат (0,0), чтобы получить новые координаты пикселей
- Теперь поместите изображение обратно в квадрант 4 (изображение может быть больше и, возможно, потребуется больше сместить после поворота.
- изменить систему координат обратно на y вниз является положительной системой координат.
Оказывается (и вы можете убедиться в этом сами, взяв карточку размером 3x5 и повернув ее на листе бумаги), использование диагонального измерения для новой ширины и высоты всегда будет достаточно большим, чтобы вместить новое повернутое изображение.
So here is a function that rotates an image:
from math import sin, cos, sqrt, pi
from tkinter import *
#returns a rotated PhotoImage
def rotatedPhotoImage(img, angle):
angleInRads = angle * pi / 180
diagonal = sqrt(img.width()**2 + img.height()**2)
xmidpoint = img.width()/2
ymidpoint = img.height()/2
newPhotoImage = PhotoImage(width=int(diagonal), height=int(diagonal))
for x in range(img.width()):
for y in range(img.height()):
# convert to ordinary mathematical coordinates
xnew = float(x)
ynew = float(-y)
# shift to origin
xnew = xnew - xmidpoint
ynew = ynew + ymidpoint
# new rotated variables, rotated around origin (0,0) using simoultaneous assigment
xnew, ynew = xnew*cos(angleInRads) - ynew*sin(angleInRads), xnew * sin(angleInRads) + ynew*cos(angleInRads)
# shift back to quadrant iv (x,-y), but centered in bigger box
xnew = xnew + diagonal/2
ynew = ynew - diagonal/2
# convert to -y coordinates
xnew = xnew
ynew = -ynew
# get pixel data from the pixel being rotated in hex format
rgb = '#%02x%02x%02x' % img.get(x, y)
# put that pixel data into the new image
newPhotoImage.put(rgb, (int(xnew), int(ynew)))
# this helps fill in empty pixels due to rounding issues
newPhotoImage.put(rgb, (int(xnew+1), int(ynew)))
return newPhotoImage
а вот эта функция используется для поворота, например, на 34 градуса.
root = Tk()
mycanvas = Canvas(root, width=300, height=300)
mycanvas.pack()
myPhotoImage=PhotoImage(file="car.png")
myPhotoImage=rotatedPhotoImage(myPhotoImage,34)
canvasImage=mycanvas.create_image(150,150,image=myPhotoImage)
Here is a screenshot of the rotated car:
Here is an example of spinning a car image using the function. Note that it will take a bit to convert the images, depending on size, but after this an array of images is available. For this car image:
мне требуется 3 секунды, чтобы преобразовать изображения.
from math import sin, cos, sqrt, pi
from tkinter import *
from time import sleep
def getImageIndex(angle):
# resets 360 to 0
angle = angle % 360
# using 16 images: 360/16 is 22.5 degrees, 11.25 is 1/2 of 22.5
index = (angle-11.25)/22.5 + 1
index = int(index)
index = index % 16
return index
root = Tk()
originalImage = PhotoImage(file="car.png")
carImages = []
for i in range(16): # using 16 images
angle = i*22.5 # 360degrees/16 images = 22.5 degrees
carImages.append(rotatedPhotoImage(originalImage, angle))
mycanvas = Canvas(root, width=300, height=300)
mycanvas.pack()
canvasimage = mycanvas.create_image(150, 150, image=carImages[0], anchor=CENTER)
for i in range(1440): #spins 4 times around
mycanvas.delete(canvasimage)
canvasimage = mycanvas.create_image(150, 150, image=carImages[getImageIndex(i)], anchor=CENTER)
root.update()
sleep(.001)
root.mainloop()
Notice that the car background is not transparent.
Кажется, это ограничено только использованием базового модуля tkinter, но если вы можете обойтись заменой цвета, такого как белый, например, ничем вместо использования альфы (r, g, b, a), вот немного более продвинутая версия функции, пропускающей запись пикселей выбранного вами цвета, и пример ее использования.
Here is the car, I have changed its background color to white.
Here is a program, clears the white pixels, makes an array of images and rotates them on an orange background. The function used in this program contains an optional background color remover.
from math import sin, cos, sqrt, pi
from tkinter import *
from time import sleep
# returns a rotated PhotoImage
def rotatedPhotoImage(img, angle, colorToMakeTransparentInHexFormat=""):
angleInRads = angle * pi / 180
diagonal = sqrt(img.width()**2 + img.height()**2)
xmidpoint = img.width()/2
ymidpoint = img.height()/2
newPhotoImage = PhotoImage(width=int(diagonal), height=int(diagonal))
for x in range(img.width()):
for y in range(img.height()):
# convert to ordinary mathematical coordinates
xnew = float(x)
ynew = float(-y)
# shift to origin
xnew = xnew - xmidpoint
ynew = ynew + ymidpoint
# new rotated variables, rotated around origin (0,0) using simoultaneous assigment
xnew, ynew = xnew*cos(angleInRads) - ynew*sin(angleInRads), xnew * sin(angleInRads) + ynew*cos(angleInRads)
# shift back to quadrant iv (x,-y), but centered in bigger box
xnew = xnew + diagonal/2
ynew = ynew - diagonal/2
# convert to -y coordinates
xnew = xnew
ynew = -ynew
# get pixel data from the pixel being rotated in hex format
rgb = '#%02x%02x%02x' % img.get(x, y)
if rgb != colorToMakeTransparentInHexFormat:
# put that pixel data into the new image
newPhotoImage.put(rgb, (int(xnew), int(ynew)))
# this helps fill in empty pixels due to rounding issues
newPhotoImage.put(rgb, (int(xnew+1), int(ynew)))
return newPhotoImage
def getImageIndex(angle):
# resets 360 to 0
angle = angle % 360
# using 16 images: 360/16 is 22.5 degrees, 11.25 is 1/2 of 22.5
index = (angle-11.25)/22.5 + 1
index = int(index)
index = index % 16
return index
root = Tk()
originalImage = PhotoImage(file="carwhitebg.png")
carImages = []
for i in range(16): # using 16 images
angle = i*22.5 # 360degrees/16 images = 22.5 degrees
carImages.append(rotatedPhotoImage(originalImage, angle,"#ffffff"))
mycanvas = Canvas(root, width=300, height=300,bg="orange")
mycanvas.pack()
canvasimage = mycanvas.create_image(150, 150, image=carImages[0], anchor=CENTER)
for i in range(943): #some arbitrary number of degrees
mycanvas.delete(canvasimage)
canvasimage = mycanvas.create_image(150, 150, image=carImages[getImageIndex(i)], anchor=CENTER)
root.update()
sleep(.001)
root.mainloop()
And here is an example of that car on an orange background:
So hopefully I have added some value to trying to answer this question.
- Я не использовал PIL
- Я разрешил поворот на произвольный угол
- Я показал демонстрацию вращающейся машины
- Я дополнительно показал, как выбрать цвет, чтобы сделать изображение прозрачным.