Blog
Skia-python: Pythonのための2Dグラフィックスライブラリ
AI Labの山口です。こちらの記事ではPythonのために開発した2Dグラフィックスライブラリ、skia-pythonの紹介をします。
概要
Skia-pythonはオープンソースで開発している高機能な2Dグラフィックスライブラリで、解像度に依存しないベクター形式で描画処理を記述することが可能です。もともとChromiumプロジェクトで描画バックエンドとしてC++で開発されていたグラフィクスライブラリのSkiaをPythonから呼び出せるようにラップして実装されています。Linux、Mac、Windowsでクロスプラットフォーム動作し、CPUおよびGPUデバイスでレンダリングをすることができます。PNG/JPEG/WEBPなどのラスタ画像形式での入出力の他、PDF/SVG形式での出力もサポートしています。
Skia-pythonを開発した背景として、Python環境でのベクターグラフィックスの取り扱いの難しさがありました。Pythonで簡単な描画機能を実現するものとして、例えばよく利用されるPillowライブラリのImageDrawモジュールにはかなり限られた描画機能しか実装されていませんし、SciPy系のscikit-imageではver 0.17.1時点でBezíer曲線のアンチエイリアス処理すら実装されていません。ゲームエンジンを想定したマルチメディアライブラリのpygameやpygletには描画機能も含まれますが、どちらかといえばGUIアプリケーションの実装を目的としたAPIとなっており、単純な描画だけを目的とすると利用しにくいものになっています。最近の研究に目を向ければDeep Learning系フレームワークで動作するDifferential renderingによる描画機能の実現例は見られますが、あくまで研究段階であり機能は限られます。このような課題を解決し、Pythonで高機能、高性能なグラフィックス描画機能を実現するためにskia-pythonを開発しました。
基本的な使い方
Skia-pythonはpipを使ってインストールすることが可能です。
1 |
pip install skia-python |
簡単な使い方として、以下に青い正方形を描画する例を載せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import skia surface = skia.Surface(128, 128) with surface as canvas: rect = skia.Rect(32, 32, 96, 96) paint = skia.Paint( Color=skia.ColorBLUE, Style=skia.Paint.kFill_Style) canvas.drawRect(rect, paint) image = surface.makeImageSnapshot() image.save('output.png', skia.kPNG) |
SkiaのAPIはCanvasに対する描画コマンドを中心に設計されています。描画デバイスとしてSurfaceを作成し、これを描画キャンバスとして扱います。描画コマンドは基本的にシェイプとペイントを受け取り、これをキャンバスに描画します。例では長方形のシェイプと青色への塗りのペイントを指定していますが、それ以外にも様々な描画コマンドが実装されています。キャンバスへの描画結果はラスタ画像として出力することができ、例ではPNG画像として出力しています。
主な描画機能
Skia-pythonは2Dグラフィックスに必要なほとんどの機能を実装しています。
-
- 長方形や楕円などのシェイプやベジェ曲線などのPathの描画
- ラスタ画像の貼り付け
- 並進や回転などの幾何変換
- システムのフォントを利用したテキストの描画
- パスの変形などの複合的なエフェクト
- 単色やグラデーションでの塗りを指定するシェーダ
- 多数のブレンディングモード
Skia-pythonがラップしているSkiaライブラリはブラウザのバックエンドとして開発されているため、HTML/CSSやSVGドキュメントを描画するために必要な2DグラフィックスAPIを網羅しています。詳細なAPIの使い方はドキュメントサイトに公開されています。
Python互換性
C++のSkia APIに加え、Skia-pythonにはNumPy配列を直接扱うためのAPIがあります。以下の例では、NumPy配列を直接キャンバスバックエンドとして描画します。
1 2 3 4 5 6 |
import numpy as np array = np.zeros((320, 240, 4), np.uint8) canvas = skia.Canvas(array) paint = skia.Paint(AntiAlias=True, Color=skia.ColorCYAN) canvas.drawCircle(180, 50, 25, paint) |
また、描画結果をNumPy配列としてエクスポートする機能もあります。
1 |
array = canvas.toarray() |
PIL/Pillowフォーマットへの変換もサポートしています。
1 2 3 4 5 6 7 8 9 |
import PIL skia_image = skia.Image.open('/path/to/input.png') pil_image = PIL.Image.fromarray(skia_image.convert(alphaType=skia.kUnpremul_AlphaType)) skia_image = skia.Image.frombytes( pil_image.convert('RGBA').tobytes(), pil_image.size, skia.kRGBA_8888_ColorType, ) |
詳細はNotebookの例を確認してみてください。
ベンチマーク評価
skia-pythonの描画速度はどうでしょうか?簡単な描画での実行速度を比較してみました。
- 実行環境:Apple Macbook Pro (15”, 2016, 2.9 GHz Intel Core i7), CPython 3.7
- 実行時間計測方法:Jupyterにて計測
%timeit –n1000 –r7
- CPUレンダリングのみ
直線の描画
灰色の直線を斜めに二本クロスさせます。
Pillow実装
1 2 3 4 5 6 7 8 9 |
from PIL import Image, ImageDraw def graycross_pil(): canvas = Image.new("RGBA", (256, 256), color=(0, 0, 0, 255)) draw = ImageDraw.Draw(canvas) draw.line((0, 0) + canvas.size, width=2, fill=(128, 128, 128)) draw.line((0, canvas.size[1], canvas.size[0], 0), width=3, fill=(128, 128, 128)) del draw return canvas |
Skia-python実装
1 2 3 4 5 6 7 8 9 |
def graycross_skia(): surface = skia.Surface(256, 256) with surface as canvas: paint = skia.Paint(Color=skia.Color(128, 128, 128), Style=skia.Paint.kStroke_Style, StrokeWidth=3.) canvas.clear(skia.ColorBLACK) canvas.drawLine((0, 0), (surface.width(), surface.height()), paint) canvas.drawLine((surface.width(), 0), (0, surface.height()), paint) return surface.makeImageSnapshot() |
実行時間
Pillow 7.2.0 | 82.1 µs ± 6.7 µs |
Skia-python 85.0 | 67.3 µs ± 3.06 µs |
Skia-pythonはPillowよりも若干速い結果となりました。
テキストの描画
Hello Worldの二行を描画します。
Pillow実装
1 2 3 4 5 6 7 8 9 10 11 |
from PIL import Image, ImageDraw, ImageFont def composite_pil(): base = Image.new("RGBA", (256, 256), color=(0, 0, 0, 255)) txt = Image.new("RGBA", base.size, (255, 255, 255, 0)) fnt = ImageFont.truetype("Times", 40) d = ImageDraw.Draw(txt) d.text((10, 10), "Hello", font=fnt, fill=(255, 255, 255, 128)) d.text((10, 60), "World", font=fnt, fill=(255, 255, 255, 255)) out = Image.alpha_composite(base, txt) return out |
Skia-python実装
1 2 3 4 5 6 7 8 9 10 11 |
def composite_skia(): surface = skia.Surface(256, 256) with surface as canvas: font = skia.Font(skia.Typeface("Times"), 40) paint = skia.Paint(Color=skia.Color(255, 255, 255, 128), AntiAlias=True) canvas.clear(skia.ColorBLACK) canvas.drawString("Hello", 10, 10 + 40, font, paint) paint.setColor(skia.Color(255, 255, 255, 255)) canvas.drawString("World", 10, 60 + 40, font, paint) return surface.makeImageSnapshot() |
実行時間
Pillow 7.2.0 | 3.12 ms ± 37.7 µs |
Skia-python 85.0 | 91.6 µs ± 9.76 µs |
Skia-pythonはPillowの34倍もの実行速度となりました。大きな差が出た理由としてフォントの読み込み結果のキャッシュなどが影響していそうですが、それ以外でもskia-pythonの方がベクトル形式のグラフィックスの扱いでは有利そうです。
おわりに
Skia-pythonは多目的に使える2Dグラフィックスライブラリです。ちょっとしたテキストやシェイプの描画だけでなく、2Dグラフィックスを用いたゲームやグラフ描画のバックエンドとして利用することが可能です。
AI Labではskia-pythonを日々の研究に活用しています。例えばテキスト認識(OCR)を行う機械学習モデルの学習データを生成するために使っています。skia-pythonを使うことで容易に様々なフォントでスタイルやエフェクトを切り替えながら多様な文字を画像として描画することができます。このほかにも広告バナーのデザインを描画したりする目的で研究に活用しています。
Skia-pythonはオープンソースで開発しています。興味のある方はGithubレポジトリもぜひチェックしてみてください。
Author