2014年2月24日月曜日

面積比で拡大縮小させるエクステンション

はじめに

下の画像のようなグラデーショントーンを作ったのですが、ドットのサイズを一つ一つ計算するのが手間で、面積比を入力するだけでリサイズできるエクステンションを作ってみました。

資料はほぼ英語のみで非常に苦戦しましたが、大体予定通りのものができたので一度出力して、まとめる際の備忘録を作っておきます。

ソース

yusai_scalingwitharearatio.inx

<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
    <_name>Scaling With Area Ratio</_name>
    <id>org.ekips.filter.yusai_scalingratio</id>
    <dependency type="executable" location="extensions">yusai_scalingwitharearatio.py</dependency>
    <dependency type="executable" location="extensions">inkex.py</dependency>
    <param name="ratio" type="float" precision="2" min="0.01" max="999" _gui-text="Scaling Ratio(Percent)">100.0</param>
    <effect needs-live-preview="false">
        <object-type>all</object-type>
        <effects-menu>
            <submenu _name="Yusai"/>
        </effects-menu>
    </effect>
    <script>
        <command reldir="extensions" interpreter="python">yusai_scalingwitharearatio.py</command>
    </script>
</inkscape-extension>

yusai_scalingwitharearatio.py

#!/usr/bin/env python 

#Copyright (C) 2014 Yusai
#This program is free software

import inkex, simpletransform, cubicsuperpath
from math import *
#
def getCenterOfD(node):
    pos = simpletransform.refinedBBox(cubicsuperpath.parsePath(node.get('d')))
    return [(pos[0] + pos[1]) / 2, (pos[2] + pos[3]) / 2]
#
def scalingPrimitive(node, attr, ratio):
    for x in attr:
        node.set(
            inkex.addNS(x, 'sodipodi'),
            str(float(node.get(inkex.addNS(x, 'sodipodi'))) * ratio)
        )
#
def scalingPath(node, ratio):
    center = getCenterOfD(node)
    simpletransform.applyTransformToNode(
        [[1.0, 0.0, -center[0]], [0.0, 1.0, -center[1]]],
        node
    )
    simpletransform.applyTransformToNode(
        simpletransform.parseTransform('scale({0},{0})'.format(str(ratio))),
        node
    )
    simpletransform.applyTransformToNode(
        [[1.0, 0.0, center[0]], [0.0, 1.0, center[1]]],
        node
    )
#
def scalingRect(node, ratio):
    wx0, wy0 = float(node.get('width')), float(node.get('height'))
    wx1, wy1 = wx0 * ratio, wy0 * ratio
    node.set('width', str(wx1))
    node.set('height', str(wy1))
    node.set('x', str(float(node.get('x')) + (wx0 - wx1) / 2))
    node.set('y', str(float(node.get('y')) + (wy0 - wy1) / 2))
#
class Scaling(inkex.Effect):
    def __init__(self):
        inkex.Effect.__init__(self)
        self.OptionParser.add_option('-r', '--ratio',
            action = 'store', type = 'float', dest = 'ratioB', default = '100.0',
            help = 'Scaling with Area Ratio.')
    def effect(self):
        if len(self.options.ids) < 1:
            inkex.debug("Select at least 1 object to use this extension.")
            return
        ratio = sqrt(self.options.ratioB / 100)
        for id, node in self.selected.iteritems():
            if node.get('transform'):
                mat = simpletransform.parseTransform(node.get('transform'))
                del node.attrib["transform"]
            else:
                mat = ''
            if node.tag == inkex.addNS('path','svg'):
                nodetype = node.get(inkex.addNS('type', 'sodipodi'))
                if nodetype == 'arc':
                    scalingPrimitive(node, ['rx', 'ry'], ratio)
                elif nodetype == 'star':
                    scalingPrimitive(node, ['r1', 'r2'], ratio)
                else:
                    scalingPath(node, ratio)
            elif node.tag == inkex.addNS('rect', 'svg'):
                scalingRect(node, ratio)
            if mat:
                simpletransform.applyTransformToNode(mat, node)
#
e = Scaling()
e.affect()

少し長いですね。Download

仕様・問題点等

  • 一応パス・円・星・四角全てに対応、フォントは要パス化・グループ解除
  • 複数選択可能、ただし個別に適用
  • グループには非対応、バウンディングボックスを拾うのが面倒だと思ったので
  • transform属性が残ってしまう。消す処理が形状ごとにばらばらで処理を作るのが面倒

備忘録

  • transformを消したかったがnode.remove('transform')は使えなかった。del node.attrib['transform']で代用
    属性を消す場合は、del node.attrib['attr']を使う。node.remove('element')は属性ではなくノードそのものを削除するためのメソッド。
    例:node.getparent().remove(node)でノードを削除できる。
  • namespacesがある時はaddNSを使う。例えばsodipodi:xならinkex.addNS('x', 'sodipodi')とする
  • たくさん開いた検索窓は別にまとめる

その他

標準付属モジュールを調べるのに手間取ったので、その他諸々含め、次のエクステンションを作る時にながら作業でworksかどこかにまとめたいと思います。