21-й час. Функции рисования библиотеки Tk, II

21-й час

Функции рисования библиотеки Tk, II

  В этой главе мы продолжим изучение функций рисования библиотеки Tk и рассмотрим средства управления цветом и создания простейшей анимации. Изучив материал данной главы, Вы получите общие представления о программировании цвета и сможете добавлять в объект холста простейшую анимацию.

Цвет

  На тему управления цветом в программах написаны сотни, если не тысячи книг. Для тех, кто хочет постичь основы это вопроса, можно порекомендовать начать с книги Чарльза Поинтона (Charles Poynton) Digital Video.

  Мы лишь в общих чертах рассмотрим некоторые общие принципы управления цветом, так как для более детального их рассмотрения пришлось бы написать отдельную книгу. Если же Вы захотите профессионально овладеть этим мастерством, то Вам потребуются годы практики (по крайней мере не 24 часа).

  Приготовьтесь к стремительному броску к истокам программирования цвета. В 1931 г. в CIE (Le Commission Internationale de L'Eclairage — Международная комиссия по вопросам освещения) была разработана математическая модель цвета, предложенная в качестве международного стандарта. Основываясь на том, что глаза человека воспринимают цвет фоторецепторами трёх типов (если Вы помните из курса биологии средней школы, эти фоторецепторы называются колбочками), в модели СIЕ было предложено изображать все цвета как точки в трёхмерном пространстве. Таким способом можно представить в цифровом выражении любой видимый цвет. Далее мы ограничимся рассмотрением аналогичной цветовой схемы, называемой RGB (red-green-blue — красный-зеленый-синий). Именно эта схема наиболее часто используется в компьютерной графике. Схема RGB линейна, т.е. все три оси равнозначны и разделены на единичные отрезки равной длины. Глаза человека по-разному воспринимают изменение интенсивности освещения — в зависимости от уровня освещенности и длины световой волны. Именно поэтому не все цвета, видимые глазом, можно корректно воспроизвести в модели RGB. Только модель СIЕ обладает достаточной сложностью, чтобы смоделировать цветное зрение человека. Все остальные модели были получены как компромисс между требованием к точности цветопередачи и экономичностью модели. Учитывалась также область применения модели. Например, для телевидения была разработана и используется специальная цветовая модель YIE. Но она не используется в компьютерной графике, поэтому мы и не рассматриваем её в этой книге. Графическое представление модели RGB показано на рис. 21.1. К сожалению, на рисунке модель стала черно-белой с оттенками серого. Чтобы оценить всю гамму красок, запустите на своём компьютере программу ccube.py.

Рис. 21.1. Графическое представление цветовой схемы RGB

  *Прим. В. Шипкова: в оригинале рисунок был другой, но поскольку он близко не похож, счёл уместным его заменить.

  В цветном изображении на экране компьютера Вы увидите, как чёрный цвет плавно переходит в красный по переднему левому вертикальному ребру куба, зеленый переходит в желтый по заднему левому вертикальному ребру и т.д. Код программы ccube.py показан в листинге 21.1.

Листинг 21.1. Программа ccube.py

  *Прим. В. Шипкова: Ещё один длинный листинг, который лень переделывать. Кому надо - брать здесь. Длина листинга более 270 строк.

  (Если цветовая палитра дисплея — 256 цветов, то значение ncolors следует изменить на 16, иначе цвета в кубе будут отображаться неправильно.) Большая часть кода должна быть Вам хорошо знакома по предыдущим главам, в которых мы изучали различные графические объекты библиотеки Tk. Так, в программе Вам повстречались уже знакомые функции рисования прямоугольников и многоугольников в объекте Canvas(холст). В строках 221-223 создаётся кнопка Выход, которая добавляется в холст с помощью функции Window(). Единственное, что Вам до сих пор не встречалось, — это повторяющиеся выражения, первое из которых появляется в строке 11:

cm = "#%02X%02X%02X"%(brl[0], brl[l], brl[2]).

  В результате выполнения этого выражения получается строка, которая может выглядеть как "1000000" для черного цвета или "IFFFFFF" — для белого. Для Tkinter цвета должны задаваться либо ключевыми словами, такими как "red", "white" или "black", либо строковыми значениями цветов модели RGB, которые начинаются с символа#. Следующие за этим символом шесть цифр определяют координаты цвета в трехмерном пространстве RGB. Первые две цифры представляют красную составляющую цвета, вторая пара цифр — зеленую, а третья пара — синюю составляющую. Таким образом, тёмно-красный цвет можно задать значением "#800000" — половинное значение яркости красной составляющей при полном отсутствии зеленой и синей. В функциях листинга 21.1 все стороны куба подразделены на 32 сегмента (число градаций цвета). Каждый сегмент, представляющий свой цвет, прорисовывается с помощью функции Rectangle() или Polygon(). Цветовые диапазоны для всех сторон куба показаны в табл. 21.1 (в том же порядке, в каком они создаются).

Таблица 21.1. Цветовые диапазоны сторон куба

Сторона куба Цветовой диапазон Функция
От зеленого к синему (задняя нижняя, слева направо) #00FF00-#00FFFF GreenCyan()
От зеленого к желтому (задняя слева, снизу вверх) #00FF00-#FFFF00 GreenYellow()
От желтого к белому (задняя верхняя, слева направо) #FFFF00-#FFFFFF YellowWhite()
От белого к синему (задняя правая, снизу вверх) #FFFFFF-#00FFFF WhiteCyan()
От черного к голубому (передняя нижняя, слева направо) #000000-#0000FF BlackBlue()
От черного к красному (передняя слева, снизу вверх) #000000-#FF0000 BlackRed()
От красного к пурпурному (передняя верхняя, слева направо) #FF0000-#FF00FF RedMagenta()
От пурпурного к голубому (передняя правая, снизу вверх) #FF00FF-#0000FF MagentaBlue()
От черного к зеленому (нижняя левая, спереди назад) #000000-#00FF00 BlackGreen()
От голубого к синему (нижняя правая, спереди назад) #0000FF-#00FFFF BlueCyan()
От фасного к желтому (верхняя левая, спереди назад) #FF0000-#FFFF00 RedYellow()
От пурпурного к белому (верхняя правая, спереди назад) #FF00FF-#FFFFFF MagentaWhite()

  В результате подразделения цветовых диапазонов на 32 градации создаётся палитра, состоящая из 32768 цветов (323). Мы могли бы произвольно увеличить количество цветов в палитре, увеличив число градаций. Единственным ограничением в этом случае является максимально допустимое в Python значение числа с плавающей запятой. Представьте себе куб RGB как хранилище денег дядюшки Скруджа, где каждая монета или банкнот описывается уникальными координатами в трехмерном пространстве, в результате чего имеет уникальный цвет. Максимальная возможность хранилища вместить как можно больше денег зависит только от размеров монет или банкнот (по мультфильму помню, что дядюшка Скрудж предпочитал монеты). Видеокарты большинства компьютеров позволяют оперировать цветовыми палитрами от 16 до 16777216 цветов. В последнем случае стороны куба подразделяются на 256 цветовых градаций — максимальный набор, который можно представить с помощью строковых цветовых значений библиотеки Tkinter.

  Цветной куб RGB наглядно иллюстрирует геометрический принцип оцифровки цвета, но на этой модели плохо видны переходы между цветами. Давайте рассмотрим другую модель, известную Вам со школьных уроков по физике, — цветное колесо. Пример такого колеса показан на рис. 21.2. Часто цветное колесо состоит из сегментов смежных цветов, но в нашем случае задан плавный градиентный переход между цветами.

  *Прим. В. Шипкова: учебник несколько устарел. Питон свободно оперирует цветами, и рисунок, который был приведён в оригинале пришлось заменить.

Рис. 21.2. Цветное колесо

  Верхняя часть колеса красная. Под углом 120° вправо представлен сектор зеленого цвета, а под углом 120° влево — синего цвета. Это основные цвета модели RGB. Голубой сектор находится на противоположной стороне колеса от красного, пурпурный противостоит зеленому, а желтый — синему. Эти цвета называются комплементарными. Чтобы получить цветное колесо на экране своего компьютера, выполните программу tkwheel.py, код которой представлен в листинге 21.2.

Листинг 21.2. Программа tkwheel.py

from Tkinter import *
from Canvas import Rectangle, Oval, Arc, Window
from colormap import *
import sys

cmap=SetupColormap0(360)

root=Tk()
cv=Canvas(root, width=401, height=401, borderwidth=0,\

  highlightthickness=0)
ar=Oval(cv, 0, 0, 400, 400)
for i in range(360):
  e=(i+90)%360
  ps = Arc(cv, 0, 0, 400, 400, start=e,extent=1,\

    fill=cmap[i], outline="")

def die(event=0):
  sys.exit(0)

button=Button(cv, text="Quit", foreground="red",\

  background="black", command=die)
Window(cv,380,20,window=button)
cv.pack()

root.mainloop()

  В строке 15 вычисляется угол начала сектора (как Вы помните, сектор является типом по умолчанию для метода Аrc()). По умолчанию для всех секторов, дуг и хорд угол отсчитывается от исходной вертикальной позиции вправо. Чтобы сектор начинался там, где он должен быть, следует выполнить предварительные вычисления, что мы и делаем в строке 15. Ещё одной новой для Вас инструкцией является вызов функции SetupColormap0() в строке 8. Эта функция происходит из модуля colorraap, который импортируется в строке 5. Код этого модуля не показан в книге, поскольку для того чтобы выводить цвета в опредёленном порядке, в модуле происходит преобразование цветовой модели RGB в HLS (hue-lightness-saturation - тон-яркость-насыщенность). Модель HLS значительно удобнее для сортировки цветов, чем модель RGB, но обсуждение этой темы выходит за рамки данной книги. Модуль colormap.py, как и многие другие полезные модули, можно найти на сервере по адресу http://www.pauahtun.org/. Чтобы использовать различные методы модуля colormap, совсем не обязательно знать, как они работают. Просто следует указать требуемое число цветов, и любая из функций SetupColormapn() возвратит список заданного числа цветов С дополнительным последним чёрным цветом (зачем добавляется чёрный цвет, Вы поймете, когда мы будем рассматривать наборы Мандельброта в главе 23).

  В модели RGB легко построить шкалу серого цвета. В оттенках серого в равной степени представлены компоненты красного, зеленого и синего цветов, отличаясь от других оттенков серого только по яркости. В листинге 21.3 показан код функции SetupColormap6().

Листинг 21.3. SetupColormap6()

def SetupColormap6(ncolors):

  сmар=[]

  xd=1.0/ncolors

  for i in range(0,ncolors):

    r=g=b=0xFF-((i*xd)*0xFF)

    cmap.append("#%02X%02X%02X"%(r,g,b))

    сmар.append("#000000")

  return сmар

  Как видите, эта функция действительно очень проста. Длина шкалы полутонов определяется как единичная (1.0) и делится на заданное число цветов. В результате получаем значение, приращения яркости при переходе от одного оттенка серого к следующему. Скорее всего, Вы даже не вспомнили об оттенках серого, когда рассматривали переливающийся всеми цветами радуги куб на рис. 21.1. Но если провести диагональ от черного утла к белому, то получится линия, каждая точка которой описывается одинаковыми значениями по осям R, G и В. В строке 6 мы умножаем полученное значение приращения яркости (xd) на порядковый номер оттенка в шкале полутонов (1), а затем умножаем полученное значение на 0xFF, чтобы привести числовое значение к формату значений RGB. Полученная шкала оттенков серого показана на рис. 21.3.

  *Прим. В. Шипкова: здесь также пришлось заменить оригинальный рисунок, так как он тоже, мягко говоря ;) устарел.

Рис. 21.3. Шкала серых полутонов

В листинге 21.4 показан код программы создания шкалы серых полутонов.

Листинг 21.4. Программа fountain.ру

import sys, string

from Tkinter import *
from Canvas import Rectangle, Window
from colormap import *

def die(event=0):
  sys.exit(0)
 

ncolors=256
w=512
h=64
 

root=Tk()
cv=Canvas(root, width=w, height=h, borderwidth=0,\

  highlightthickness=0)
cmap=Graymap(ncolors)
wd=w/ncolors
x=y=0
for i in range(ncolors):
  Rectangle(cv, x, y, x+wd, y+h, fill=cmap[i], width=0,\

    outline="")
  x=x+wd
qb=Button(root,text="Quit",command=die)
item=Window(cv,450,32,window=qb)
cv.pack()
root.mainloop()

  Если необходимо, чтобы чёрный цвет в шкале полутонов находился слева, а не справа, достаточно в строке 17 для возвращенной схемы цветов вызвать метод cmap.reverse(). Все списки в Python поддерживают набор стандартных методов, таких как sort() и reverse (). В строке 17 мы также вызываем функцию Graymap(), что соответствует вызову уже знакомой Вам функции SetupColormap6 (). Эта функция возвращает список строковых цветовых значений Tkinter, которые затем используются для установки цвета заливки объектов Rectangle, образующих шкалу полутонов. В данном случае цветовые значения соответствуют оттенкам серого, а прямоугольники, представляющие каждый полутон, имеют 2 пикселя в ширину и 64 пикселя в высоту. Подобные шкалы оттенков серого используются во многих приложениях, например в графических редакторах.

  Но в наше время полноцветных цифровых изображений построение шкалы цветных полутонов, пожалуй, будет ещё более интересной задачей. Это совсем не сложно, тем более, что мы уже рассмотрели набор необходимых функций при построении цветного колеса на рис. 21.2. (Если нужно отобразить на экране в цвете одну из сторон куба 'RGB' подумайте, как можно было бы это сделать без методов модуля colormap?) На рис. 21.4 показан результат подмены в строке 17 вызова функции Graymap() на функцию SetupColormap0(). (В коде нужно будет сделать ещё некоторые минорные изменения, показанные в листинге 21.5).

  *Прим. В. Шипкова: и опять пришлось заменить оригинальный рисунок на более свеженький. ;) Что-то мне подсказывает, придётся описывать цветовые функции отдельно. :)

Рис. 21.4. Цветная шкала полутонов

Код программы целиком показан в листинге 21.5.

Листинг 21.5. Программа colorfountain.py

import sys, string

from Tkinter import *
from Canvas import Rectangle, Window
from colormap import *

def die(event=0):
  sys.exit(0)
 

ncolors=256
w=256
h=256
root=Tk()
frame=Frame(root)
cv=Canvas(frame, width=w, height=h, borderwidth=0,\
  highlightthickness=0)
cmap=SetupColormap0(ncolors)
wd=w/ncolors
x=y=0
for i in range(ncolors):
  Rectangle(cv,x,y,x+wd,y+h,fill=cmap[i],width=0,outline="")
  x = x+wd
  qb=Button(frame, text="Exit", command=die,\
    foreground="red", background="black")
frame.pack()
cv.pack(side=TOP)
qb.pack(side=LEFT)
root.mainloop()

  Данный код почти ничем не отличается от рассмотренного в листинге 21.4. Исключение состоит только в установке ширины и высоты объекта Canvas. Запустите программу colorfountain.py, чтобы посмотреть, как выглядит шкала цветных полутонов. В следующих разделах мы воспользуемся ею в качестве фона холста при создании простейшей анимации.

Анимация

  В 1972 г. я был студентом колледжа. Помню, как я сидел в "Папа Чарли" (кафе в центре греческих кварталов Чикаго) в компании друзей после утомительных выходных, которые мы провели, роясь в карьерах и железнодорожных насыпях в поисках окаменелостей. Окаменелости нам были нужны для выполнения практического курса по геологии. Напротив нас стояли игральные машины, на мониторах которых видны были летающие фильярдные шары. Я не был любителем этой игры, но не мог устоять перед магией электронной игрушки и сыграл пару игр. Игра мне быстро надоела, но, видимо, с этого момента компьютеры запали в мою душу. До этого единственный компьютер, на котором мне приходилось работать, был монстр, заполнявший собой целый зал и отличавшийся весьма посредственными возможностями. Тут же передо мной стояли сравнительно небольшие машины, которые не только воспроизводили красочные графические- изображения, но и позволяли управлять ими. В следующем семестре я проходил курс по зооархеологии. Профессор предложил нам занести в компьютер базу данных по идентификации нескольких тысяч окаменелых костей животных, которые мы собрали во время экспедиции в Турцию. У профессора были знакомые, которые могли затем выполнить статистическую обработку данных, и он надеялся получить таким образом ошеломляющие результаты. Проект не был доведён до конца, но я вновь столкнулся с компьютерами и с тех пор не расстаюсь с ними.

  Видимо, под влиянием юношеских воспоминаний мне пришла в голову идея, что для обучения анимации лучше всего будет написать программу, воспроизводящую движение шаров по бильярдному полю. Предполагалось, что в бильярд должны играть несколько игроков с виртуальными киями. Впрочем, эта идея мне быстро надоела, потому что я не любитель бильярда. Поэтому свою программу я затем преобразовал в поле, по которому вместо шаров летали фигурки древних богов индейцев Майя (надо же мне было применить мою коллекцию тотемных изображений, с которой Вы скоро познакомитесь). А чтобы порадовать глаз, зеленое бильярдное поле мы сделаем переливающимся всеми цветами радуги (Вы научились делать это в предыдущем разделе).

  Но, пожалуй, это будет многовато для одной главы. Так что летающих богов оставим до следующей главы, а пока займёмся созданием исходного игрового поля.

  Чтобы соответствовать реальности, наш шар при столкновении с краями поля должен отскакивать под тем же углом. Другими словами, угол падения должен быть равен углу отражения. Если шар наткнулся на боковую стенку под углом в 45°, то он должен отскочить под углом в -45°. Вы увидите, что так и происходит (рис. 21.5), когда запустите программу tkpool.py, код которой показан в листинге 21.6.

import sys, string

from Tkinter import *
from Canvas import Rectangle, Line, ImageItem, \

   CanvasText
from math import *

w=128.0
h=256.0
xp=0
yp=h
xdelta=2.0
ydelta=-2.0
wball=None
filename="white.gif"
tm=None

img=None
txt=None

def Radians(x):
 
return (x*(pi/180.0E0))

def Degrees(x):
 
return (x*(180.0/pi))

def die(event=0):
  sys.exit(0)


def moveball(*args):
 
global cv, img, h, w, wball, xp, yp, xdelta, ydelta,\

     tm
  cv.move(wball, xdelta, ydelta)
 
if xp>=0.0 and yp>=0.0:
   
if xp>=w:
      xdelta=xdelta*-1.0
    xp=xp+xdelta
    yp=yp+ydelta
    tm=cv.after(10, moveball)
 
else:
    cv.delete(wball)
    tm=None

def chsize(event):
 
global w, h, xp, yp, xdelta, ydelta
  w=event.width
  h=event.height

def pause(event=0):
 
global tm
  cv.after_cancel(tm)

def shoot(event):
 
global cv, img, h, w, wball, xp, yp, tm
  global xdelta, ydelta, filename, txt
  img=PhotoImage(file=filename)
  xp=event.x
  yp=event.y
  xdelta=2.0
 
if yp<h/2.0:
   
if ydelta<0:
      ydelta=ydelta*-1.0
 
else:
   
if ydelta>0:
      ydelta=ydelta*-1.0
  a=yp-h/2.0
  bw=w-xp
# A - угол между направлением полета шара и

# перпендикуляром, опущенным к правому -краю окна
# а - высота получившегося треугольника, сторона а;
# b - длина перпендикуляра, сторона b;
# с - длина гипотенузы, сторона с.
#
Все три тригонометрические функции _could_

# возвращают О
# поэтому их было бы не плохо заключить в

# конструкцию try...except.
A=atan2(a,bw)
deg=Degrees(A)
b=2.0/tan(A)
xdelta=abs(b)
cv.delete(wball)
cv.delete(txt)
wball=ImageItem(cv, xp, yp, image=img)
txt=CanvasText(cv, w-25, h-25, font="Helvetica 14",
text="%02d"%int(deg))
tm=cv.after(10,moveball)

if __name__=="__main__":
  root=Tk()
  cv=Canvas(root, width=w, height=h, borderwidth=0,\
  background="#409040", highlightthickness=0)
  cv.bind("<Configure>", chsize)
  cv.bind("<Button-1>", shoot)
  cv.bind("<Button-3>", pause)
  root.bind("<Escape>", die)
  cv.pack(expand=1, fill=BOTH)
  root.mainloop()

 

  *Прим. В. Шипкова: и этот рисунок постигла участь прежних - был заменён на более свежий.

Рис. 21.5. Электронный бильярдный стол

  В строках 83-93 создаётся обычное окно Tk с объектом холста внутри. В строке 91 для объекта холста сv устанавливаются свойства расширяемости и обязательное заполнение всей поверхности корневого окна-контейнера. В результате пользователь получает возможность изменять размеры окна, перетаскивая мышью его стороны или углы, после чего игровое поле автоматически изменит свой размер в соответствии с новыми размерами окна. Поскольку для работы программы необходимо знать текущие габариты поля, следует определить функцию, которая будет запускаться каждый раз при изменении размера окна. Для этого в строке 92 используется стандартный метод bind() для связывания функции chsize с событием "<Configure>". В строках 44-47 мы видим, что функция chsize() возвращает и записывает для нас текущие размеры ширины и высоты холста.

  Метод bind() используется также в строке 90 для установки вызова функции die() нажатием клавиши <Esc>. Таким образом, можно завершить выполнение программы, благодаря чему нам не нужно устанавливать на поле кнопку Закрыть или Выход. Обратите внимание, что в этот раз вызывается метод bind() корневого окна и именно ему присваивается функция обработки события die(). Дело в том, что корневое окно перехватывает все нажатия клавиш и не передает их вложенному объекту холста.

  В строке 89 метод bind() используется для назначения выполнения функции shoot() событию щелчка мыши где-либо в пределах холста. В данном приложении именно на функцию shoot() возложена основная нагрузка. В результате выполнения этой функции из места щелчка мыши будет вылетать шар в направлении середины правого края окна. Ударившись о стенку, этот шар будет отлетать под тем же углом. Ниже мы подробно рассмотрим все нюансы работы функции shoot().

  Прежде всего, в строке 52 функция shoot() извлекает графическое изображение шара, используя для этого функцию PhotoImage(), которую мы рассмотрели в предыдущей главе. В данном месте программе может быть назначен любой графический файл в формате GIF, лишь бы он был в форме квадрата и не очень большой. Следующая задача выполняется в строках 53, 54 и состоит в том, чтобы возвратить координаты щелчка мыши и присвоить их переменным хр и yр. Эти значения необходимы для выполнения функции moveball(). Эта функция вызывает метод move() объекта Canvas. Методу move() в списке параметров передаётся переменная wball, которая указывает положение изображения шара на экране. Перемещение шара контролируется двумя другими параметрами xdelta и ydelta. В физике и математике принято обозначать минимальные векторные изменения (направления или скорости) греческой буквой Д (дельта), почему мы и выбрали такие названия для переменных. В строках 56-61 определяется направление, в котором должен перемещаться шар. Шар будет перемещаться вверх, если щелчок мышью был сделан в нижней половине поля, или вниз, если щелчок был сделан в верхней половине поля. Поскольку нам нужно также определить угол, под которым будет перемещаться шар, придётся освежить в памяти знания по тригонометрии. Однако обучение Вас основам тригонометрии выходит за рамки этой книги. (Возможно, Вам в этом помогут Web-страницы, приведенные в разделе "Примеры и задания" в конце данной главы.) Сейчас просто поверьте мне, что для выполнения подобных задач Вам будет необходимо знание тригонометрических функций.

  Для выполнения тригонометрических функций нужны некоторые исходные данные, такие как длина основания и высота правильного треугольника, описывающего положение объекта. После щелчка мышью мы получаем две координаты х и у, по которым следует построить правильный треугольник. Высота треугольника вычисляется в строке 62: а=ур-h/2.0 (в этой программе мы всегда нацеливаем шар в середину правого края окна). Длина основания треугольника вычисляется в строке 63: bw=w-хр. На рис. 21.6 показаны традиционные названия сторон и вершин правильного треугольника.

Рис. 21.6. Правильный треугольник

  То, что мы называли "основанием треугольника", показано на рисунке стороной b, a высота — это сторона а. Сторона с называется гипотенузой (вспомните теорему Пифагора, она Вам как раз сейчас понадобится). Поскольку нам известны длины сторон а и Ь, не составит труда вычислить угол А, что мы и делаем в строке 71:А=atan2(a,bw). Вместо функции atan2() можно использовать стандартную функцию atan() из модуля math (он импортируется в строке 7:from math import *). Но в этом случае предварительно нужно разделить длину стороны а на b. Функция atan2() выполняет это деление за Вас перед тем, как вычислить угол А. Применительно к окну нашего приложения вершина А соответствует точке щелчка мышью, а В — середине правой стороны окна. Вершина С соответствует проекции точки щелчка мышью на правую сторону окна.

  Поскольку все функции модуля math оперируют и возвращают значения углов, выраженные в радианах, а не в градусах, нам необходима функция преобразования радиан в градусы. Эта функция записана в строках 25, 26. Замкнутая окружность соответствует 360 градусам, или 6,28318530718... радиан. Это равенство можно выразить формулой 360°=2pi. Для преобразования значений, выраженных в радианах, в углы используется функция Degrees(), а для обратного преобразования — Radians() (хотя последняя в данном варианте программы не используется). В строке 72 вызывается функция Degrees(), и возвращенный результат сохраняется в переменной deg.

  В строке 74 вычисляется минимальное смещение по оси X с помощью выражения b=2.0/tan(A). Для вычисления используются тангенс определённого ранее угла А и значение минимального смещения по оси У, которое устанавливается равным двум пикселям. Смещение должно быть небольшим, чтобы сделать плавным движение шара на экране. Результат этого выражения может быть как положительным, так и отрицательным. Поскольку наш шар всегда должен двигаться вправо, следует использовать абсолютное значение полученного результата, для чего и нужна строка 75:xdelta=abs(b). На этом этапе мы выполнили все необходимые предварительные вычисления.

  Строки 76, 77 удаляют старое изображение шара и старый текст, если таковые присутствовали на холсте. В строке 78 новое изображение шара устанавливается в холсте по координатам щелчка мышью. В строках 79, 80 создаётся надпись, которая размещается в нижнем левом углу окна и указывает угол, под которым был запущен шар.

  За перемещение шара отвечает функция cv.after(10, moveball), которая вызывается в строке 81. Метод after() объекта Canvas устанавливает таймер 10 миллисекунд, после чего вновь повторяется вызов функции moveball(). Возвращаясь к функции moveball() в строках 32-42, мы видим, что основная её задача состоит в вызове метода move() для объекта wball, созданного в строке 78 функцией shoot(). Объект смещается по осям X и Y на вычисленные нами ранее значения xdelta и ydelta.

  *Прим. В. Шипкова: если угол будет близок к 90 градусам, будет заметно ускорение шара, а если близок к нулю - будет еле ползти. Дело в том, что программа не контролирует длину гипотенузы. Что, методически, является ошибкой.

  Кроме того, функция определяет, не достиг ли объект правого края окна. Для этого достаточно контролировать только координату х. В строке 36 текущая координата объекта по оси X сравнивается с шириной окна. Если шар достиг края окна, то значение xdelta просто умножается на -1, в результате чего шар начинает двигаться под тем же углом, но влево. Как видите, для программирования зеркального отражения достаточно просто поменять знак смещения по оси X. В результате, если шар подлетел к краю окна под углом 45°, то далее он продолжит путь под углом 135°. Чтобы математически вычислить угол отражения, нужно просто от 180° отнять угол падения.

  *Прим. В. Шипкова: посколькумодель шара проста, то простительно, а вообще, нужно учитывать шероховатость стола и бортика стола, точку попадания кия по шару (левее-правее, выше-ниже, полученный вращательный момент). Так что шарик описАть, не забор опИсать. ;)

Резюме

  В этой главе мы изучили основы программирования цвета в модели RGB, а затем научились создавать шкалу полутонов в оттенках серого и в цвете. Мы также научились использовать объект Canvas для создания простейшей анимации и убедились, что для этого нам просто необходимо вспомнить известные со школьной скамьи тригонометрические функции.

  В следующей славе мы продолжим работу над анимацией, несколько усложнив нашу задачу, а затем, в главе 23, перейдем к изучению наборов Мандельброта.

Практикум

Вопросы и ответы

  Неужели движение бильярдных шаров по полю можно смоделировать с помощью таких простых функций? Тогда можно было бы стать чемпионом, имея в кармане простенький калькулятор.

  В действительности вычисление углов — это самая простая часть моделирования игры в бильярд. Кроме того, нам следовало бы учесть вращение шара вокруг своей оси (по вертикали и горизонтали), трение шара о покрытие бильярдного стола, а также дрожание рук во время удара кием по шару. Теория вероятности в этом случае гораздо лучше сможет помочь в выборе победителя, чем простые тригонометрические формулы. Впрочем, если Вы хотите попробовать смоделировать игру в бильярд, я совершенно не хочу Вас останавливать, тем более, если Вы хорошо знакомы с теорией вероятности. Это должно быть очень увлекательно.

  В школе тригонометрия казалась мне чем-то непостижимым. Действительно ли тригонометрические вычисления так просто выполнять?

  Сегодня выполнение тригонометрических вычислений стало значительно проще, чем это было во времена, когда у людей не было калькуляторов и компьютеров. В школе Вам, вероятно, приходилось пользоваться тригонометрическими таблицами. Действительно, отыскивать каждый раз нужное значение по таблице, например, какому углу соответствует значение тангенса, равное 0,488673541719, очень нудно. Теперь достаточно использовать встроенную в Python функцию atan() или atan2() (или воспользоваться далеко не самым сложным калькулятором), чтобы определить искомый угол: 24,4439547804°.

  *Прим. В. Шипкова: в моей школе по крайней мере до 1995 года использовали таблицы Брадиса. ;) Да и у меня эти таблицы лежат где-то на полке .

Контрольные вопросы

  1. Какие цвета будут комплементарными для следующих первичных цветов: красного, зеленого и синего?

    а) Желтый, оранжевый и лиловый.

    б) Коричневый, розовый и фиолетовый.

    в) Желтый, пурпурный и голубой.

    г) Антикрасный, антизеленый и антисиний.

  2. Как проще всего изменить на противоположное направление перехода цветового градиента шкалы, показанной на рис. 21.4?

    а) Нужно изменить угол насыщенности в модуле colormap.

    б) Применить к списку цветовых значений метод reverse().

    в) Изменить начальный индекс в цикле обращений к списку цветовых значений.

    г) Выбрать другую цветовую модель.

  3. Что произойдет, если изменить знак на противоположный для параметров xdelta и ydelta функции moveball() в программе tkpool.py?

    а) Шар улетит за край окна.

    б) Шар полетит в сторону левого края окна и отразится от него.

    в) Шар исчезнет.

    г) Шар возвратится обратно по тому же пути до точки щелчка мышью, а потом за неё.

Ответы

  1. в. Для красного, зеленого и синего цветов (они являются первичными в модели RGB) комплементарными будут желтый, пурпурный и голубой. В других цветовых моделях установлены иные первичные цвета. Например, для модели YMC первичными являются желтый, пурпурный и голубой. Подумайте, как в таком случае в модели YMC будут называться красный, зеленый и синий цвета?

  2. 6. Поскольку все функции модуля colormap возвращают обычные для Python списки, к ним можно применить стандартный метод reverse(), чтобы сменить последовательность элементов в списке на противоположную. Вариант с также возможен, но, по-моему, это более сложное решение.

  3. г. В результате изменения знаков параметров xdelta и ydelta на противоположные шар полетит в обратном направлении к точке щелчка мышью.

Примеры и задания

  Лучшее место в Internet, с которого можно начать освоение основ программирования цветовой информации, — домашняя страница Charles Poynton's Color Technology, расположенная по адресу http://www.inforamp.net/~poynton/Poynton-color.html.

  Чтобы освежить в памяти свои знания по тригонометрии, посетите страницу по адресу http://aleph0.clarku.edu/~djoyce/java/trig/.

  Этот же источник поможет Вам разобраться, почему в тригонометрических функциях проще использовать значения в радианах, а не в градусах.

  Попробуйте самостоятельно изменить программу tkpool.py таким образом, чтобы переменная xdelta всегда была равной 2,0, a ydelta — вычислялась.