Kivyのcanvas命令でDocumentationに載ってない事、ややこしい事

# はじめに

Kivyのgraphics周りのDocumentationは抜けているところが多いので、ソースコードを読まないとわからないことが多いです。その上ソースコードはcython用なので、私のEditor(SublimeText)の持つソースコード参照機能や`__doc__`参照機能が使えず、調べるのに時間を要する事が多いです。なので自分なりによく使う機能や忘れやすい事をまとめました。

canvas命令全体図

![all.png](https://qiita-image-store.s3.amazonaws.com/0/144673/e043deb2-2212-2def-6bf7-d529db0706a0.png)

# InstructionGroup

[doc](https://kivy.org/docs/api-kivy.graphics.instructions.html#kivy.graphics.instructions.InstructionGroup), [src](https://github.com/kivy/kivy/blob/687c973172512867e77b52ec4164dd628a39bc9f/kivy/graphics/instructions.pyx#L123-L230)
さっそくですがDocに載っていない物があります。ただもしかすると内部用の物で意図的に載せてないのかもしれません。

- `length()`  ...持っている描画命令の数を返すMethod
- `indexof(inst)` ...instが内部の描画命令のlist上の何番目に位置するかを返すMethod

また~~methodではないので明らかに内部用ですが~~、便利そうなので次の属性も挙げておきます。

- `children` ...持っている描画命令の`list`

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
FloatLayout:
    canvas:
        Rectangle:
            size: self.size
            pos: self.pos
''')

canvas = root.canvas

print('命令の総数は', canvas.length())
for inst in canvas.children:
    print(inst)
print('{}の序数は{}'.format(inst, canvas.indexof(inst)))

runTouchApp(root)
```

```text:標準出力
命令の総数は 2
<kivy.graphics.context_instructions.BindTexture object at 0x7f14b7a8ef78>
<kivy.graphics.vertex_instructions.Rectangle object at 0x7f14b7ee1688>
<kivy.graphics.vertex_instructions.Rectangle object at 0x7f14b7ee1688>の序数は1
```

# Callback

[doc](https://kivy.org/docs/api-kivy.graphics.instructions.html#kivy.graphics.instructions.Callback),[src](https://github.com/kivy/kivy/blob/687c973172512867e77b52ec4164dd628a39bc9f/kivy/graphics/instructions.pyx#L420-L558)

canvas命令ですがFactoryに登録されていないのでKv言語からは利用できません。

### 追記2018/07/29

最新版(master branch)のKivyではKv言語からも利用可能になりました。

```text
Widget:
    canvas:
        Callback:
            callback: lambda __: setattr(self, 'callback_test', 'TEST')
```

# ScissorPush, ScissorPop

[doc](https://kivy.org/docs/api-kivy.graphics.scissor_instructions.html),[src](https://github.com/kivy/kivy/blob/master/kivy/graphics/scissor_instructions.pyx)

描画範囲を指定した長方形内に制限します。

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
FloatLayout:
    canvas:
        ScissorPush:
            size: int(self.width *0.7), int(self. height *0.7)
            pos: 0, 0
        Ellipse:
            size: self.size
            pos: self.pos
        ScissorPop:
''')

runTouchApp(root)
```

![Screenshot at 2018-04-24 18-31-20.png](https://qiita-image-store.s3.amazonaws.com/0/144673/53626e35-75b5-e591-1f44-0ae3e0f1545b.png)

`ScissorPush`に渡す長方形の座標は

1. **int型で指定しないといけない**
2. **常にWindow座標系**

なのが注意点。またここでもDocの書き忘れがあり、それは`x`,`y`を纏めて設定できる`pos`と`width`,`height`を纏めて設定できる`size`です。

# StencilXXX系

[doc](https://kivy.org/docs/api-kivy.graphics.stencil_instructions.html),[src](https://github.com/kivy/kivy/blob/master/kivy/graphics/stencil_instructions.pyx)
ScissorXXXとは違って自由度の高い描画領域の制限が行える。

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
RelativeLayout:
    canvas:

        StencilPush:
        
        # 段階1: ここでの描画は実際には行われず、段階2での描画の範囲を決めるのにだけ使われる
        Rectangle:
            size: self.width * 0.7, self.height * 0.7
            pos: 0, 0
        Ellipse:
            size: self.width * 0.7, self.height * 0.7
            pos: self.width * 0.3, self.height * 0.3

        StencilUse:

        # 段階2: 段階1で決められた範囲制限のもと、描画が行われる
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            size: self.size
            pos: 0, 0

        StencilUnUse:

        # 段階3: ここでは段階1と全く同じ描画を行わなければならない。正しく後始末する為にこの手順が必要らしいです。
        Rectangle:
            size: self.width * 0.7, self.height * 0.7
            pos: 0, 0
        Ellipse:
            size: self.width * 0.7, self.height * 0.7
            pos: self.width * 0.3, self.height * 0.3

        StencilPop:
''')

runTouchApp(root)
```

![Screenshot at 2018-04-24 19-02-17.png](https://qiita-image-store.s3.amazonaws.com/0/144673/b711dc0c-90a7-d3f1-2f86-512d760af511.png)

楕円と長方形の重なっている部分が描画範囲から外れています。多分**段階1で奇数回描画されたPixelのみが段階2における描画可能な範囲になる**のだと思います。

# VertexInstruction

![vertex_instruction.png](https://qiita-image-store.s3.amazonaws.com/0/144673/2d002b46-688d-c6c9-0860-b7cb9af24108.png)

[doc](https://kivy.org/docs/api-kivy.graphics.html), [src](https://github.com/kivy/kivy/blob/master/kivy/graphics/vertex_instructions.pyx), [Lineのsrc](https://github.com/kivy/kivy/blob/master/kivy/graphics/vertex_instructions_line.pxi)

`VertexInstruction`は`source`と`texture`というpropertyを持っています。`Rectangle`や`Mesh`等で何気なく使っていたこのpropertyはここで実装されたものでした。という事は`Line`や`Point`にもTextureを貼り付けれるはずです。

### LineへのTexture貼り付け

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
RelativeLayout:
    canvas:
        Line:
            points:
                [
                0, 0,
                self.width / 3, self.height,
                self.width * 2 / 3, 0,
                self.width, self.height,
                ]
            width: 30
            source: 'python-logo.png'
''')

runTouchApp(root)
```

![Screenshot at 2018-04-24 19-43-46.png](https://qiita-image-store.s3.amazonaws.com/0/144673/1cbaa142-a267-88a3-6967-cf1356bbb946.png)

### PointへのTexture貼り付け

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
RelativeLayout:
    canvas:
        Point:
            points:
                [
                0, 0,
                self.width / 3, self.height,
                self.width * 2 / 3, 0,
                self.width, self.height,
                ]
            pointsize: 100
            source: 'python-logo.png'
''')

runTouchApp(root)
```

![Screenshot at 2018-04-24 19-51-07.png](https://qiita-image-store.s3.amazonaws.com/0/144673/6c01cb4d-8cdf-24d7-39be-f5772d4b6054.png)
簡易な粒子表現に使えそうです。

### 図形の塗りつぶし と 線の描画

通常長方形で塗りつぶす時は`Rectangle`を使い、辺のみを書きたい時には`Line`を使う事になると思います。この時のパラメータの渡し方が異なっていて非常にややこしいのでメモしておきます。

#### 長方形

```kivy:長方形の塗りつぶし
Rectangle:
    pos: self.pos
    size: self.size
```

```kivy:長方形の線だけ
Line:
    rectangle: [*self.pos, *self.size, ]  # python3.5以降じゃないと使えない構文
```

#### 楕円

```kivy:楕円の塗りつぶし1
Ellipse:
    pos: self.pos
    size: self.size
```

```kivy:楕円の線だけ1
Line:
    ellipse: [*self.pos, *self.size, ]
```

```kivy:楕円の塗りつぶし2(扇型)
Ellipse:
    pos: self.pos
    size: self.size
    angle_start: 0
    angle_end: 270
```

```kivy:楕円の線だけ2(扇型)
Line:
    ellipse: [*self.pos, *self.size, 0, 270, ]
```

#### 角の丸い長方形

```kivy:角の丸い長方形の塗りつぶし1
RoundedRectangle:
    pos: self.pos
    size: self.size
    radius: [30, ]
```

```kivy:角の丸い長方形の線だけ1
Line:
    rounded_rectangle: [*self.pos, *self.size, 30, ]
```

```kivy:角の丸い長方形の塗りつぶし2
RoundedRectangle:
    pos: self.pos
    size: self.size
    radius: [(10, 10), (40, 40), (100, 100), (200, 200), ]
```

```kivy:角の丸い長方形の線だけ2
Line:
    rounded_rectangle: [*self.pos, *self.size, 200, 100, 40, 10, ]  # 注意: radiusの部分の順番と形式が異なる
```

#### 真円

```text:真円の塗りつぶし
真円に特化した塗りつぶし命令は無いのでEllipseで代用する
```

```kivy:真円の線だけ1
Line:
    circle: [*self.center, min(*self.size) / 2]
```

```kivy:真円の線だけ2(扇型)
Line:
    circle: [*self.center, min(*self.size) / 2, 0, 270]
```

### 他

`Quad`は四角形を描く命令です。`Mesh`は[別の記事](https://qiita.com/gotta_dive_into_python/items/b051f8a088bbdea39751)で取り扱った事があるので割愛します。

# ContextInstruction

![context_instruction.png](https://qiita-image-store.s3.amazonaws.com/0/144673/36e02f90-00b6-ba4c-ef42-a2295373d261.png)

[doc](https://kivy.org/docs/api-kivy.graphics.context_instructions.html), [doc](https://kivy.org/docs/api-kivy.graphics.html), [src](https://github.com/kivy/kivy/blob/master/kivy/graphics/context_instructions.pyx)
自分でShaderを書かないのならContextInstructionの中で使う物は `Scale`, `Rotate`, `Translate`, `Color`, `PushMatrix`, `PopMatrix` の６つだけになると思います。この内の`Color`以外の５つは行列を操作する命令で、詳しくは[過去の記事](https://qiita.com/gotta_dive_into_python/items/cfe39bf067f1c5e2e200)にて書いたので割愛します。

# Clear

Clearは`Instruction`の派生Classではありません。それどころかClassですらありません。これはKv言語のcanvas内でのみ使える特殊な描画命令です。

```python3
from kivy.lang import Builder
from kivy.app import runTouchApp


root = Builder.load_string(r'''
Label:
    text: 'TEST'
    canvas:
        Color:  # A
            rgb: 0, 1, 0  # A
        Clear:  # B
        Rectangle:
            size: self.size
            pos: self.pos
            texture: self.texture
''')


runTouchApp(root)
```

![Screenshot at 2018-07-29 03-01-08.png](https://qiita-image-store.s3.amazonaws.com/0/144673/6816c040-97ad-0076-59af-94910f18439b.png)

B行を消した場合の結果

![Screenshot at 2018-07-29 03-06-44.png](https://qiita-image-store.s3.amazonaws.com/0/144673/16c8095e-55d2-0688-345c-3fc864b7b5ff.png)

`Clear`によって`Clear`の前に追加された描画命令であるColor(A行)と`Label`が元々持っていた描画命令が消されていた事が分かります。