タグ別アーカイブ: Clutter

screentone 2D and 3D

コミックブックアーカイブビューアを作っている筆者でありますが。
スクリーントーンの描写が変になる場合があることに困っていた。

Clutter(OpenGL ES) でリサイズしているのが悪いのかな?
と思い GdkPixbuf をリサイズしてからレンダリングで解決した。
よかったよかった、じゃない!もう少し調べよう。

Jトーン 網点(ドットパターン) J-00|スクリーントーン 通販【ジェイトーン・ネットショッピング】

トーンは上記のサンプル画像をお借りしてと。

#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Clutter", "1.0")
gi.require_version("GtkClutter", "1.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl

class DrawWindow(Gtk.Window):
    """
        2D and 3D View Check
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # tone
        self.pixbuf = GdkPixbuf.Pixbuf.new_from_file("tone.jpg")
        # cairo (2D)
        self.da = Gtk.DrawingArea()
        self.da.connect("draw", self.on_draw)
        # OpenGL ES (3D)
        embed = GtkClutter.Embed()
        stage = embed.get_stage()
        stage.set_background_color(Clutter.Color.new(0, 0, 0, 255))
        stage.connect("allocation-changed", self.on_stage_allocation_changed)
        self.actor = Clutter.Actor()
        image = Clutter.Image()
        image.set_data(
            self.pixbuf.get_pixels(),
            Cogl.PixelFormat.RGBA_8888 if self.pixbuf.get_has_alpha() else Cogl.PixelFormat.RGB_888,
            self.pixbuf.get_width(),
            self.pixbuf.get_height(),
            self.pixbuf.get_rowstride()
        )
        self.actor.set_content(image)
        stage.add_child(self.actor)
        # set
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(self.da, True, True, 0)
        vbox.pack_start(embed, True, True, 0)
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()

    def on_stage_allocation_changed(self, actor, box, flags):
        aw = box.x2
        ah = box.y2
        w = self.pixbuf.get_width()
        h = self.pixbuf.get_height()
        if aw * h > ah * w:
            width = w * ah / h
            height = ah
            x = (aw - width) / 2
            y = 0
        else:
            width = aw
            height = h * aw / w
            x = 0
            y = (ah - height) / 2
        self.actor.set_size(width , height)
        self.actor.set_position(x, y)

    def on_draw(self, widget, cr):
        d_width = widget.get_allocated_width()
        d_height = widget.get_allocated_height()
        cr.set_source_rgb(0, 0, 0)
        cr.rectangle(0, 0, d_width, d_height)
        cr.fill()
        p_width = self.pixbuf.get_width()
        p_height = self.pixbuf.get_height()
        if (d_width * p_height) > (d_height * p_width):
            width = p_width * d_height / p_height
            height = d_height
        else:
            width = d_width
            height = p_height * d_width / p_width
        pixbuf = GdkPixbuf.Pixbuf.scale_simple(self.pixbuf, width, height, GdkPixbuf.InterpType.BILINEAR)
        Gdk.cairo_set_source_pixbuf(cr, pixbuf, (d_width-width)/2, (d_height-height)/2)
        cr.paint()

GtkClutter.init()
DrawWindow()
Gtk.main()

動かす。

上半分が cairo で下が Clutter です。
拡大はまったく同じ描写になるけど縮小は全然違うものになることが解る。
なんだよこの 3D 描写。。。。。

画像処理や 3D に詳しいわけではないので細かい解説はできないけど。
単なるラスターデータとして扱う 2D と RGB として扱う 3D の違いだろう。
網点を RGB 色として認識した結果こんな悲惨なことに。

とにかく 3D では小さめな画像かアニメ調のベタ塗りでないとこんなことになる。
スクリーントーンを多用するコミック表示には徹底的に向いていないようです。

Clutter で cairo が使える手段があるのはそういうことか。
cairo で作り替えしよ。

ClutterGst Rotation

今時のスタンドアロンな動画プレイヤーには回転機能が必須だ。
なんたって回転編集していないスマホ動画を結構見かけるようになった。
昨年まではスマホ動画が主体になるなんて思いもしなかったのに。

ということで、ClutterGst で映像の回転を行う関数を探してみよう。

Clutter Gst 3.0.24 Reference Manual: Clutter Gst 3.0.24 Reference Manual

無いんカイ!

生 Gst を直で使うなら手段があった気がするんだがこれは困った。
いやまて、ClutterActor 自体を回転させればいいんでないの?
そのための OpenGL ES じゃないか、ということで実験コード。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Clutter = imports.gi.Clutter;
const ClutterGst = imports.gi.ClutterGst;
const GtkClutter = imports.gi.GtkClutter;

ClutterGst.init(null);

// width:640 height:800
const PATH = "/home/sasakima-nao/movie/GF/動く苗ちゃん(1).mp4";

const RotateWindow = new Lang.Class({
    Name: 'RotateWindow',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        // player
        this.player = new ClutterGst.Playback();
        this.player.set_filename(PATH);
        this.content = new ClutterGst.Content();
        this.content.set_player(this.player);
        this.actor = new Clutter.Actor();
        this.actor.set_content(this.content);
        // embed
        let embed = new GtkClutter.Embed();
        embed.get_stage().add_child(this.actor);
        this.add(embed);
        // size
        this.resize(450, 450);
        this.actor.set_width(320);
        this.actor.set_height(400);
        this.actor.set_position(65, 25);
        // rotation
        this.actor.set_pivot_point(0.5, 0.5);
        this.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, 90);
        //
        this.show_all();
        this.player.set_playing(true);
    }
});

let app = new Gtk.Application();
app.connect("activate", function() {
    new RotateWindow(app);
});
app.run(null);

ピボットポイントを中心にして Z 軸で回せば映像も普通に回転するね。
リサイズ時には 90/270 度の時に縦横の値を入れ替えるだけでイケそうだ。
もっと正しい手段があるかもだけど筆者はコレでいいや。

あぁやっと完全放置だった Y901x を更新するネタができたぞい。
だって Youtube でオッサンが田舎道をバイクで走っているだけのばかり見ていたし。
ドラマやアニメより個人制作な素人動画のほうが妙に面白いよね。

Clutter Constraint

Clutter のドキュメントの中で Constraint がよく解らない。
Google 翻訳によると「制約」、っていったい何の制約なのだろう?
検索検索。

Clutter の Constraint (制約条件) – ふとしの日記

Gjs で動かないって new キーワードの場合引数はプロパティを JSON 指定ですし。
JSON 引数に書き換えたら普通に動いた、多分今は自身で解っていると思うけど。
五年前のこの当事はみんな手探り状態だったししかたがないね。

Clutter.SnapConstraint

えっとつまり。
Constraint ってアクションを他の Actor と連動させるって解釈でいいのかな?
この名前が不適切なのか、実は英語ではコレで合っているということなのかシラネ!

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const GtkClutter = imports.gi.GtkClutter;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;

const ConstraintWin = new Lang.Class({
    Name: 'ConstraintWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            x: 25,
            y: 25,
            width: 50,
            height: 50,
            reactive: true
        });
        this.blue = new Clutter.Actor({
            background_color: Clutter.Color.new(0, 0, 255, 255),
            width: 50,
            height: 50,
        });
        // DnD
        this.red.add_action(new Clutter.DragAction());
        // Constraint
        let bind = new Clutter.BindConstraint({
	        coordinate: Clutter.BindCoordinate.POSITION,
	        offset: 50,
            source: this.red
        });
        this.blue.add_constraint(bind);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.red);
        stage.add_child(this.blue);
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new ConstraintWin(app);
});
app.run(null);

で赤いブロックをドラッグする。
うん、見事にオフセットされたまま連動してドラッグされるんだね。

ClutterAlignConstraint は Totem のボタン類みたいな用途で使えそう。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const GtkClutter = imports.gi.GtkClutter;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;

const TotemLikeWin = new Lang.Class({
    Name: 'TotemLikeWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        //
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            width: 200,
            height: 50,
            reactive: true
        });
        stage.add_child(this.red);
        // Constraint
        let x = new Clutter.AlignConstraint({
	        align_axis: Clutter.AlignAxis.X_AXIS,
	        factor: 0.5,
            source: stage
        });
        let y = new Clutter.AlignConstraint({
	        align_axis: Clutter.AlignAxis.Y_AXIS,
	        factor: 0.8,
            source: stage
        });
        this.red.add_constraint(x);
        this.red.add_constraint(y);
        //
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new TotemLikeWin(app);
});
app.run(null);

いくらリサイズしても常に同じ位置に貼り付ける動作がこんなにアッサリ!
多分こういうのが本来の使い方だと思う。

Clutter Animation

ClutterActor はアニメーション機能を内蔵している。

clutter_actor_save_easing_state # pause
clutter_actor_set_easing_duration # time
# move, resize, opacity, etc…
clutter_actor_restore_easing_state # start

たったコレだけで様々な変更がアニメーションになって動く。
ということでサンプルコード、クリック毎に Actor が入れ替わります。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const GtkClutter = imports.gi.GtkClutter;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;

const AnimateWin = new Lang.Class({
    Name: 'AnimateWin',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        this.red = new Clutter.Actor({
            background_color: Clutter.Color.new(255, 0, 0, 255),
            x: 25,
            y: 25,
            width: 50,
            height: 50
        });
        this.blue = new Clutter.Actor({
            background_color: Clutter.Color.new(0, 0, 255, 255),
            x: 75,
            y: 75,
            width: 50,
            height: 50
        });
        // action
        let click = new Clutter.ClickAction();
        click.connect("clicked", Lang.bind(this, function() {
            // pause
            this.red.save_easing_state();
            this.blue.save_easing_state();
            // animation time (default 250)
            this.red.set_easing_duration(2500);
            // move
            let [x, y] = this.red.get_position();
            let [x2, y2] = this.blue.get_position();
            this.red.set_position(x2, y2);
            this.blue.set_position(x, y);
            // play
            this.red.restore_easing_state();
            this.blue.restore_easing_state();
        }));
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.red);
        stage.add_child(this.blue);
        stage.add_action(click);
        this.add(embed);
        this.show_all();
    }
});

// init
GtkClutter.init(null);

let app = new Gtk.Application();
app.connect("activate", function() {
    new AnimateWin(app);
});
app.run(null);

コレだけだと使いどころがあんまりなさそうだよね。
しかし、ClutterImage の resize が超滑らかになるメリットがあった!
ということで comipoli に早速採用、永遠に実験用アプリ…

ClutterBoxLayout

ClutterStage が現在 Wayland で使えないのはあきらめて。
当面は GtkClutter を使ったサンプルコードを書いていこう。

今回は ClutterActor のレイアウトマネージャ。

GTK+ と全然違い property の layout-manager で指定するようだ。
デフォルトは ClutterFixedLayout になっている。
書くまでもなく絶対値配置なので子 Actor は重なって表示される。

GtkBox のように並べて配置するには ClutterBoxLayout を指定。
clutter_actor_add_child するだけで普通に並んでいく。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const GtkClutter = imports.gi.GtkClutter;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;

const ListTest = new Lang.Class({
    Name: 'ListTest',
    Extends: Gtk.Window,

    _init: function() {
        this.parent({
            title: "add_actor"
        });
        // BoxLayout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            layout_manager: layout
        });
        // add
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.actor.add_child(item);
        }
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GListModel を使ってバインドもできる。
頻繁に入れ替えを行う場合はこちらのほうが便利かもしれない。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const GtkClutter = imports.gi.GtkClutter;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const Lang = imports.lang;

const ListTest = new Lang.Class({
    Name: 'ListTest',
    Extends: Gtk.Window,

    _init: function() {
        this.parent({
            title: "GListStore"
        });
        // GListStore  
        this.model = new Gio.ListStore({
            item_type: Clutter.Text
        });
        // layout
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            //pack_start: true,
            spacing: 1
        });
        // Actor
        this.actor = new Clutter.Actor({
            background_color: Clutter.Color.from_string("#aaa")[1],
            layout_manager: layout
        });
        this.actor.bind_model(this.model, Lang.bind(this, function(item) {
            return item;
        }));
        // append
        let girls = ["椎名心実です", "あかね!", "くおえうえーーーうえうぅぅぅ"];
        for (let i=0; i<girls.length; i++) {
            let item = new Clutter.Text({
                x_align: Clutter.ActorAlign.START,
                x_expand: true,
                text: girls[i]
            });
            this.model.append(item);
        }
        // remove
        this.model.remove(1);
        // Embed
        let embed = new GtkClutter.Embed();
        let stage = embed.get_stage();
        stage.add_child(this.actor);
        this.add(embed);
        // this
        this.connect("hide", Gtk.main_quit);
        this.resize(this.actor.width, this.actor.height);
        this.show_all();
    }
});

GtkClutter.init(null);
new ListTest();
Gtk.main();

GTK+ 同様に子 Actor のサイズによって親のサイズが拡大されるようだ。
ただし GTK+ 部品にまでは適用されないのでそこらは自力で。