В данном разделе мы рассмотрим преобразования. Аффинное преобразование состоит из нуля или более линейных преобразований (вращение, масштабирование или сдвиг) и смещений. Несколько линейных преобразований могут быть скомбинированы в простую матрицу. Вращение это преобразование, которое двигает твердое тело вокруг неподвижной точки. Масштабирование это трансформация, которая увеличивает или уменьшает объекты.
Коэффициент масштабирования одинаковый во всех направлениях. Перевод это преобразование, которое двигает каждую точку константного отрезка в определенном направлении. Сдвиг это трансформация, которая перемещает объект перпендикулярно к заданной оси, с большим значением на одной стороне оси, чем на другой.
Перевод
В данном примере мы продемонстрируем простое перевод.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def on_draw(self, wid, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 30, 30) cr.fill() cr.translate(20, 20) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(0, 0, 30, 30) cr.fill() cr.translate(30, 30) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(0, 0, 30, 30) cr.fill() cr.translate(40, 40) cr.set_source_rgb(0.3, 0.8, 0.8) cr.rectangle(0, 0, 30, 30) cr.fill() |
В данном примере был нарисован прямоугольник, после этого мы выполним перевод для того, чтобы нарисовать такой же прямоугольник несколько раз.
1 |
cr.translate(20, 20) |
Функция translate() меняет текущую матрицу преобразования путем смещения пользовательского пространства координат. В нашем случае мы выполнили перевод на 20 пунктов в обоих направлениях.
Сдвиг
В следующем примере мы выполним операцию сдвига. Сдвиг – это искажение объекта вдоль определенной оси. В PyCairo нет определенного метода для этой операции. Нам нужно создать собственную матрицу преобразования. Обратите внимание на то, что каждое аффинное преобразование может быть выполнено путем создания матрицы преобразования.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def on_draw(self, wid, cr): cr.set_source_rgb(0.6, 0.6, 0.6) cr.rectangle(20, 30, 80, 50) cr.fill() mtx = cairo.Matrix(1.0, 0.5, 0.0, 1.0, 0.0, 0.0) cr.transform(mtx) cr.rectangle(130, 30, 80, 50) cr.fill() |
На примере данного кода, мы выполняем простую операцию сдвига.
1 2 3 |
mtx = cairo.Matrix(1.0, 0.5, 0.0, 1.0, 0.0, 0.0) |
Данное преобразование смещает значение у на 0.5 пунктов значения х.
1 |
cr.transform(mtx) |
Мы выполняем данное преобразование при помощи метода transform() .
Масштабирование
В данном примере мы продемонстрируем операцию масштабирования. Масштабирования – это операция преобразования, в которой объект увеличивается или сжимается в размерах.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def on_draw(self, wid, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 90, 90) cr.fill() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 90, 90) cr.fill() cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 90, 90) cr.fill() |
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Мы нарисуем три прямоугольника размером 90 на 90 пикселей, на двух из них мы выполним операцию масштабирования.
1 2 3 4 |
cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 90, 90) cr.fill() |
Мы равномерно масштабируем прямоугольник с коэффициентом 0.6
1 2 3 4 |
cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 90, 90) cr.fill() |
Здесь мы выполним другую операцию масштабирования с коэффициентом 0.8. Если мы взглянем на изображение, мы можем увидеть что третий, желтый треугольник является самым маленьким. Даже если бы мы использовали наименьший коэффициент масштабирования. Так происходит по той причине, что операции преобразования являются аддитивными. На самом деле, масштабирование третьего прямоугольника было выполнено с коэффициентом 0.528 (0.6х0.8).
Разделительные преобразования
Разделительные преобразования являются аддитивные. Для того, чтобы разделить одну операцию от другой, мы воспользуемся методом save() и restore(). Метод save() создает копию текущего состояния контекста рисования и сохраняет его во внутреннем стеке сохраненных состояний.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def on_draw(self, wid, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 90, 90) cr.fill() cr.save() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 90, 90) cr.fill() cr.restore() cr.save() cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 90, 90) cr.fill() cr.restore() |
В данном примере мы масштабируем два прямоугольника. На этот раз мы разделим операции масштабирования друг от друга.
1 2 3 4 5 6 |
cr.save() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 90, 90) cr.fill() cr.restore() |
Мы разделили операцию масштабирования с применением метода scale() между нашими методами save() и restore().
Теперь третий, желтый прямоугольник стал больше чем второй, красный.
Пончик
В данном примере мы создадим комплексную фигуру, состоящую из множества Python вращающихся эллипсов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#!/usr/bin/python from gi.repository import Gtk import cairo import math class Example(Gtk.Window): def __init__(self): super(Example, self).__init__() self.init_ui() def init_ui(self): darea = Gtk.DrawingArea() darea.connect("draw", self.on_draw) self.add(darea) self.set_title("Donut") self.resize(350, 250) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", Gtk.main_quit) self.show_all() def on_draw(self, wid, cr): cr.set_line_width(0.5) w, h = self.get_size() cr.translate(w/2, h/2) cr.arc(0, 0, 120, 0, 2*math.pi) cr.stroke() for i in range(36): cr.save() cr.rotate(i*math.pi/36) cr.scale(0.3, 1) cr.arc(0, 0, 120, 0, 2*math.pi) cr.restore() cr.stroke() def main(): app = Example() Gtk.main() if __name__ == "__main__": main() |
Мы выполним операции вращения и масштабирования. Мы также будем сохранять, и восстанавливать контексты PyCairo.
1 2 3 |
cr.translate(w/2, h/2) cr.arc(0, 0, 120, 0, 2*math.pi) cr.stroke() |
Мы создаем окружность посреди окна GTK. Это будет ограничивающая окружность для наших эллипсов.
1 2 3 4 5 6 7 |
for i in range(36): cr.save() cr.rotate(i*math.pi/36) cr.scale(0.3, 1) cr.arc(0, 0, 120, 0, 2*math.pi) cr.restore() cr.stroke() |
Мы создадим 36 эллипсов вдоль всей нашей окружности, а также разделим каждую операцию масштабирования и вращения друг от друга при помощи методов save() и restore()
Звезда
В следующем примере мы покажем вращающуюся и масштабируемую звезду.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
#!/usr/bin/python from gi.repository import Gtk, GLib import cairo class cv(object): points = ( ( 0, 85 ), ( 75, 75 ), ( 100, 10 ), ( 125, 75 ), ( 200, 85 ), ( 150, 125 ), ( 160, 190 ), ( 100, 150 ), ( 40, 190 ), ( 50, 125 ), ( 0, 85 ) ) SPEED = 20 TIMER_ID = 1 class Example(Gtk.Window): def __init__(self): super(Example, self).__init__() self.init_ui() self.init_vars() def init_ui(self): self.darea = Gtk.DrawingArea() self.darea.connect("draw", self.on_draw) self.add(self.darea) self.set_title("Star") self.resize(400, 300) self.set_position(Gtk.WindowPosition.CENTER) self.connect("delete-event", Gtk.main_quit) self.show_all() def init_vars(self): self.angle = 0 self.scale = 1 self.delta = 0.01 GLib.timeout_add(cv.SPEED, self.on_timer) def on_timer(self): if self.scale < 0.01: self.delta = -self.delta elif self.scale > 0.99: self.delta = -self.delta self.scale += self.delta self.angle += 0.01 self.darea.queue_draw() return True def on_draw(self, wid, cr): w, h = self.get_size() cr.set_source_rgb(0, 0.44, 0.7) cr.set_line_width(1) cr.translate(w/2, h/2) cr.rotate(self.angle) cr.scale(self.scale, self.scale) for i in range(10): cr.line_to(cv.points[i][0], cv.points[i][1]) cr.fill() def main(): app = Example() Gtk.main() if __name__ == "__main__": main() |
В данном примере мы создали звезду и выполним над ней все упомянутые преобразования.
1 2 3 4 5 6 7 |
points = ( ( 0, 85 ), ( 75, 75 ), ( 100, 10 ), ( 125, 75 ), ( 200, 85 ), ... |
Звезда будет стоять на этих точках.
1 2 3 4 5 6 |
def init_vars(self): self.angle = 0 self.scale = 1 self.delta = 0.01 ... |
В методе init_vars() мы инициализируем три переменные. Переменная self.angle используется при вращении, self.scale при масштабировании звезды. Переменная self.delta контролирует, когда звезда будет расширяться и когда сокращаться.
1 |
glib.timeout_add(cv.SPEED, self.on_timer) |
Указывается каждая миллисекунда cv.SPEED метода on_timer() .
1 2 3 4 5 |
if self.scale < 0.01: self.delta = -self.delta elif self.scale > 0.99: self.delta = -self.delta |
Эти линии контролируют моменты, после которых звезда начнет расширяется или сжимается.
1 2 3 |
cr.translate(w/2, h/2) cr.rotate(self.angle) cr.scale(self.scale, self.scale) |
Мы смещаем звезду в центр окна, вращаем и масштабируем её.
1 2 3 4 |
for i in range(10): cr.line_to(cv.points[i][0], cv.points[i][1]) cr.fill() |
Здесь мы нарисовали звезду.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»