502
Комментарий:
|
← Версия 16 от 2015-01-18 20:42:33 ⇥
6964
|
Удаления помечены так. | Добавления помечены так. |
Строка 3: | Строка 3: |
При работе с растровой графикой очень частая операция — масштабирование объектов. Суть операции в том, чтобы точку в диапазоне от ''A,,0,,'' до ''B,,0,,'' превратить в точку в диапазоне от ''A,,1,,'' до ''B,,1,,'' с соблюдением пропорций: {{drawing:A0x0b0A1X1B1}} |
Примеры в этом тексте используют объекты из модулей `math` и `turtle`: {{{#!python from math import * from turtle import * }}} == Масштабирование == При работе с растровой графикой очень частая операция — масштабирование объектов. Суть операции в том, чтобы число ''X,,0,,'' в диапазоне от ''A,,0,,'' до ''B,,0,,'' превратить в число ''X,,1,,'' в диапазоне от ''A,,1,,'' до ''B,,1,,'' с соблюдением пропорций: {{attachment:a0b0xa1b1.png}} Значение ''X,,0,,'' сначала надо ''нормализовать'': перевести из диапазона ''A,,0,,…B,,0,,'' в диапазон ''0…1''. Для чего из ''X,,0,,'' надо вычесть нижнюю границу диапазона ''A,,0,,'' и поделить результат на длину диапазона ''B,,0,,-A,,0,,'': {{{X=(X0-A0)/(B0-A0)}}} Затем перевести в новый диапазон ''A,,1,,…B,,1,,'' теми же операциями в обратном порядке. Получаем функцию переноса с масштабированием `scale()`: {{{#!python def scale(A0,B0,A1,B1,x): return float(x-A0)/(B0-A0)*(B1-A1)+A1 }}} Проверим, как это выглядит с помощью Черепашки: {{{#!python def AxB(A,B,x,y): up() goto(A,y) down() stamp() goto(x,y) stamp() goto(B,y) stamp() up() A0,B0,X0 = -50,120,81 AxB(A0,B0,X0,40) # Теперь масштабируем X0 A1,B1 = -100,200 X1 = scale(A0,B0,A1,B1,X0) AxB(A1,B1,X1,-40) }}} == Поворот == Более подробно см [[RW:Поворот]] в Википедии. Вспомним¸ что `sin` угла и `cos` угла — это длины противолежащего и прилежащего катетов в прямоугольном треугольнике с единичной гипотенузой: {{attachment:sincos.png}} Обратим внимание, что картинка также иллюстрирует поворот на угол ''A'' против часовой стрелки точки пересечения положительной полуоси абсцисс и единичной окружности с центром в начале координат. Сравнительно несложно (поворотом этой картинки на произвольный угол) показать, что поворот ''любой'' точки на единичной окружности аналогичен, и получить следующую функцию поворота точки ''M'' на угол ''A'' относительно точки ''O'': {{{#!python def rotate(M,O,A): X=(M[0]-O[0])*cos(A)-(M[1]-O[1])*sin(A)+O[0] Y=(M[1]-O[1])*cos(A)+(M[0]-O[0])*sin(A)+O[1] return X,Y }}} Проверим. Зададим функцию рисования ломаной по точкам: {{{#!python def draw(line,col="black"): up() goto(*line[0]) down() color(col) for x,y in line[1:]: goto(x,y) up() }}} Убедимся, что поворот на угол, близкий к `pi` относительно ''(0,0)'' переносит фигуру из первого в третий квадрант (при повороте ровно на `pi` будет наблюдаться центральная симметрия): {{{#!python l = [(100,200),(200,50),(50,100),(150,50)] ll = [rotate(m,(0,0),2.75) for m in l] draw(l) draw(ll) }}} == Перенос + масштабирование + поворот ломаной == Осталось решить, какая точка в операции «перенос + масштабирование + поворот ''ломаной''» будет служить началом координат. Несложно написать функцию масштабирования+переноса фигуры, вписанной в прямоугольник с левым нижним углом в точке ''m,,0,,'' и правым верхним углом в точке ''M,,0,,'', которая порождает подобную фигуру, вписанную в прямоугольник ''m,,1,,:M,,1,,''. Однако после поворота (неважно вокруг чего) описанный прямоугольник, скорее всего, окажется другим. Логично за центр поворота взять центр результирующего прямоугольника, хотя в этом случае после поворота фигура может не вписаться в него. Получаем функцию переноса+масштабирования ломаной (списка точек) в прямоугольнику ''m:M'' и последующего поворота его на угол `alpha`: {{{#!python def rotoscale(dots,m,M,alpha): # списки абсцисс и ординат X,Y = zip(*dots) mx,Mx,my,My=min(X),max(X),min(Y),max(Y) # перенос+масштабирование всех точек ds = [(scale(mx,Mx,m[0],M[0],x),scale(my,My,m[1],M[1],y)) for x,y in dots] center = (m[0]+M[0])/2.,(m[1]+M[1])/2. # поворот всех точек относительно центра целевого прямоугольника return [rotate(d,center,alpha) for d in ds] }}} Посмотрим, как работает эта функция: {{{#!python reset() # исходный многоугольник l = [(-50,50),(30,40),(50,-30),(-30,-50),(-50,50)] draw(l,"blue") # позиция повёрнутого + масштабированного прямоугольника, размер описанного квадрата pos, w = (100,100), 50 # количество копий N = 6 for i in xrange(N): a = pi*2*i/N # где размещать очередную копию p = rotate(pos, (0,0), a) # в какой квадрат вписывать diag = ((p[0]-w/2,p[1]-w/2), (p[0]+w/2, p[1]+w/2)) ll = rotoscale(l, diag[0], diag[1], a) draw(ll,"red") # увеличим размер w+=15 }}} Другой вариант — функция, которая выдаёт фигуру, строго вписывающуюся в ''m:M'' уже ''после'' поворота. Правда, в этом случае трудно соблюсти исходные пропорции. |
Перенос, масштабирование и вращение
Примеры в этом тексте используют объекты из модулей math и turtle:
Масштабирование
При работе с растровой графикой очень частая операция — масштабирование объектов. Суть операции в том, чтобы число X0 в диапазоне от A0 до B0 превратить в число X1 в диапазоне от A1 до B1 с соблюдением пропорций:
Значение X0 сначала надо нормализовать: перевести из диапазона A0…B0 в диапазон 0…1. Для чего из X0 надо вычесть нижнюю границу диапазона A0 и поделить результат на длину диапазона B0-A0: X=(X0-A0)/(B0-A0)
Затем перевести в новый диапазон A1…B1 теми же операциями в обратном порядке. Получаем функцию переноса с масштабированием scale():
Проверим, как это выглядит с помощью Черепашки:
Поворот
Более подробно см Поворот в Википедии.
Вспомним¸ что sin угла и cos угла — это длины противолежащего и прилежащего катетов в прямоугольном треугольнике с единичной гипотенузой:
Обратим внимание, что картинка также иллюстрирует поворот на угол A против часовой стрелки точки пересечения положительной полуоси абсцисс и единичной окружности с центром в начале координат.
Сравнительно несложно (поворотом этой картинки на произвольный угол) показать, что поворот любой точки на единичной окружности аналогичен, и получить следующую функцию поворота точки M на угол A относительно точки O:
Проверим. Зададим функцию рисования ломаной по точкам:
Убедимся, что поворот на угол, близкий к pi относительно (0,0) переносит фигуру из первого в третий квадрант (при повороте ровно на pi будет наблюдаться центральная симметрия):
Перенос + масштабирование + поворот ломаной
Осталось решить, какая точка в операции «перенос + масштабирование + поворот ломаной» будет служить началом координат. Несложно написать функцию масштабирования+переноса фигуры, вписанной в прямоугольник с левым нижним углом в точке m0 и правым верхним углом в точке M0, которая порождает подобную фигуру, вписанную в прямоугольник m1:M1. Однако после поворота (неважно вокруг чего) описанный прямоугольник, скорее всего, окажется другим. Логично за центр поворота взять центр результирующего прямоугольника, хотя в этом случае после поворота фигура может не вписаться в него.
Получаем функцию переноса+масштабирования ломаной (списка точек) в прямоугольнику m:M и последующего поворота его на угол alpha:
1 def rotoscale(dots,m,M,alpha):
2 # списки абсцисс и ординат
3 X,Y = zip(*dots)
4 mx,Mx,my,My=min(X),max(X),min(Y),max(Y)
5 # перенос+масштабирование всех точек
6 ds = [(scale(mx,Mx,m[0],M[0],x),scale(my,My,m[1],M[1],y)) for x,y in dots]
7 center = (m[0]+M[0])/2.,(m[1]+M[1])/2.
8 # поворот всех точек относительно центра целевого прямоугольника
9 return [rotate(d,center,alpha) for d in ds]
Посмотрим, как работает эта функция:
1 reset()
2 # исходный многоугольник
3 l = [(-50,50),(30,40),(50,-30),(-30,-50),(-50,50)]
4 draw(l,"blue")
5 # позиция повёрнутого + масштабированного прямоугольника, размер описанного квадрата
6 pos, w = (100,100), 50
7 # количество копий
8 N = 6
9 for i in xrange(N):
10 a = pi*2*i/N
11 # где размещать очередную копию
12 p = rotate(pos, (0,0), a)
13 # в какой квадрат вписывать
14 diag = ((p[0]-w/2,p[1]-w/2), (p[0]+w/2, p[1]+w/2))
15 ll = rotoscale(l, diag[0], diag[1], a)
16 draw(ll,"red")
17 # увеличим размер
18 w+=15
Другой вариант — функция, которая выдаёт фигуру, строго вписывающуюся в m:M уже после поворота. Правда, в этом случае трудно соблюсти исходные пропорции.