Как повернуть изображение на холсте без использования PIL?

Есть ли простой способ повернуть импортированное изображение на холсте tkinter? Я бы предпочел не использовать модуль PIL, но я не могу найти жизнеспособных альтернатив. (Если это поможет, я хочу повернуть некоторые изображения автомобилей, когда они поворачивают на перекрестке.)


person Jonnyolsen1    schedule 20.12.2016    source источник
comment
tkinter не может повернуть изображение. Вы можете создавать изображения с повернутым автомобилем следующим образом: /files/forum-attachments/ и тогда вы сможете использовать его без PIL.   -  person furas    schedule 20.12.2016


Ответы (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
person acw1668    schedule 21.12.2016

Большое спасибо за ответ @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()  
person roninpawn    schedule 27.04.2019

Вот способ поворота изображения с помощью 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
  • Я разрешил поворот на произвольный угол
  • Я показал демонстрацию вращающейся машины
  • Я дополнительно показал, как выбрать цвет, чтобы сделать изображение прозрачным.
person garydavenport73    schedule 25.07.2021