Python」タグアーカイブ

PHP Imagick

さて PHP をやるといっても言語仕様なんてすぐに理解できる。
筆者はすぐ忘れる人なのでまたチップスページを作る、はいいとして。
PHP で何をやるかだ、とりあえず画像のリサイズだな。

PHPで画像をリサイズしよう!表示・保存方法もわかる | 侍エンジニア塾ブログ(Samurai Blog) – プログラミング入門者向けサイト

GD は定番として Imagick ってココ(さくら)で使えるのかな?
手持ちパソコン内だけで可能なのでは意味がないし。
と思ったけれど、どちらも問題なく使えるようだ。

【さくらのレンタルサーバ】基本仕様 ? さくらのサポート情報

ところで Fedora 自体は PHP をインストールしただけでは両方使えない。
てなわけで dnf する。

sudo dnf install php-gd php-pecl-imagick

httpd を通すのが面倒だからスクリプトで直接試す。
Linux はコレができるから楽なんだよね。

#!/usr/bin/env php

<?php

    // php-gd
    [$width, $hight] = getimagesize('test.jpg');
    $baseImage = imagecreatefromjpeg('test.jpg');
    $image = imagecreatetruecolor(100, 100);
    imagecopyresampled($image, $baseImage, 0, 0, 0, 0, 100, 100, $width, $hight);
    imagejpeg($image , 'out_01.jpg');

    // php-pecl-imagick
    $image2 = new Imagick('test.jpg');
    $image2->thumbnailImage(100, 0); // Segmentation fault...
    $image2->writeImage('out_02.jpg');
    $image2->clear();

?>

Imagick::thumbnailImage でセグメンテーション違反になってしまうヤン。
縮小画像は問題なく作成されるけど。
下記のように起動すればこのエラーは出なくなる、けどソレでいいのか?

env MAGICK_THREAD_LIMIT=1 php test.php

おまけ、cairo も使えるんだね。
PHP: Cairo – Manual
94. PHPからCairoを使う | 日経 xTECH(クロステック)

dnf で見つからないし必要性もあまり感じないので無視するけど。
せっかくなのでリンク先コードを Python3 で書き直ししてみた。
create なんてメソッドは無いのでこんなふうに。

#!/usr/bin/env python3

import cairo

surface = cairo.PDFSurface('sample.pdf', 605.0, 855.0)
cr = cairo.Context(surface)

cr.set_source_rgb (0.0, 0.0, 1.0)

cr.move_to(150, 150)
cr.select_font_face('Monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(72)
cr.show_text('日本語OK!')
cr.show_page()

# 破棄はガベージコレクション

Linux てか GNOME って本当にプログラミングは楽チンだよね。

f-string

PEP 498 — Literal String Interpolation | Python.org

f-string なんてものが Python 3.6 から使えるのか。

#!/usr/bin/env python3

suzuki = 'スズキ'

# Python2 互換
print('%sのバイクはカッコイイ' % suzuki)
# Python3 format
print('{0}はスクーターもカッコイイ'.format(suzuki))
# f-string (3.6 or lator)
print(f'{suzuki}は自動車もカッコイイ')

# raw f-string
lf = '元からある改行はダメだけど\n'
print(fr'{lf}この \n は改行されない')

# docstring
doc = 'ヒアドキュメント'
print(f'''これなら ' も " も普通に使える
まるで{doc}だね''')

# ゼロ詰め
for n in range(3):
    print(f'{n:#03d}')

なるほど。

当然だけど f-string が一番短く書ける、かつ理解しやすい。
ゼロ詰めが可能なので js のテンプレートリテラルより便利。
今後 Python はコレでいこう。

vscode だとヘンテコな色分けをしてくれます。
てか f’ と打ったら閉じクォートを勝手に補完するのをヤメてくれ!
閉じクォート補完設定は OFF にしているのに、別設定なのかよ。
本当に余計なことばかりするクソエディタだな。

私的には $ 記号が欲しかった、sh, php, js 互換になるし。

__getitem__ for in

Python で自作クラスに __getitem__ を定義すれば for 文が使えるようになる。
しかし場合によっては無限大になったり存在しない値を戻したりしかねない。

len から自前で最大値を取得して、という方法ではないようだ。
for 文はいったい何を基準で抜けているのか。

配列の範囲外を得ようとすると IndexError の例外を投げてくる。
ということはコレを利用して for 文を抜けているのだろうか。
下記の __getitem__ が無限になってしまうコードに入れて実験。

#!/usr/bin/env python3

class Test():
    def __getitem__(self, n):
        if n > 3:
            raise IndexError
        return n * 2

t = Test()

for n in t:
    print(n)

'''output
0
2
4
6
'''

なるほど。

Poppler で範囲外を指定すると None を戻すだけで例外にならず親でエラーになっていた。
cbz 等は self.namelist が IndexError を吐いて親に raise していただけ。
Python が用意してくれた例外に頼らず自身で raise していこう。

Python3 subprocess

筆者のアプリは python3 なのに Gio の subprocess を使っていた。
Gjs, Vala, C 等からでも参考になるよう、てか途中まで Gjs 製だった。

JXA では後で Swift をやるかもと NSTask を使っていた。
やらねーし、Xcode デカすぎだし Python のほうが面白いし。
PyObjC 版は Python3 の subprocess モジュールでいいんでね?

17.5. subprocess ? サブプロセス管理 ? Python 3.6.5 ドキュメント

知らない間に subprocess が超単純化されていた。
Popen とか call とか迷わず run だけでいい、破棄も wait もいらない。
unrar, 7za を使うコードを subprocess.run に書き換えしてみる。

#!/usr/bin/env python3

import objc, zipfile, re, subprocess
from AppKit import *

PICEXT = '\.(jpe?g|png|gif)$'

class ComipoliArchive:
    def __init__(self):
        self.status = 0
        self.namelist = []
        self.path = ''

    def new_archive(self, path, is_unrar, is_7za):
        self.path = path
        if re.search(r'\.(cbz|zip)$', path, re.I):
            self.status = 0
            self.namelist.clear()
            with zipfile.ZipFile(path) as o:
                l = o.namelist()
                l.sort()
                for name in l:
                    if re.search(PICEXT, name, re.I):
                        self.namelist.append(name)
        elif is_unrar and re.search(r'\.(cbr|rar)$', path, re.I):
            self.status = 1
            self.namelist.clear()
            cp = subprocess.run(['unrar', 'vt', '-p-', '--', path], stdout=subprocess.PIPE)
            lines = cp.stdout.decode('utf-8').split('\n')
            for line in lines:
                s = line.lstrip()
                if s.startswith('Name: '):
                    name = s[6:]
                    if re.search(PICEXT, name, re.I):
                        self.namelist.append(name)
        elif is_7za and re.search(r'\.(cb7|7z)$', path, re.I):
            self.status = 2
            self.namelist.clear()
            cp = subprocess.run(['7za', 'l', '-slt', path], stdout=subprocess.PIPE)
            lines = cp.stdout.decode('utf-8').split('\n')
            for line in lines:
                if line.startswith('Path'):
                    name = line[7:]
                    if re.search(PICEXT, name, re.I):
                        self.namelist.append(name)
        else:
            return False
        return True

    def __getitem__(self, num):
        if self.status == 0:
            with zipfile.ZipFile(self.path) as o:
                b = o.read(self.namelist[num])
                data = NSData.dataWithBytes_length_(b, len(b))
                return NSImage.alloc().initWithData_(data)
        elif self.status == 1:
            cp = subprocess.run(['unrar', 'p', '-inul', '-@', '--', self.path, self.namelist[num]], stdout=subprocess.PIPE)
            data = NSData.dataWithBytes_length_(cp.stdout, len(cp.stdout))
            return NSImage.alloc().initWithData_(data)
        elif self.status == 2:
            cp = subprocess.run(['7za', 'x', '-so', self.path, self.namelist[num]], stdout=subprocess.PIPE)
            data = NSData.dataWithBytes_length_(cp.stdout, len(cp.stdout))
            return NSImage.alloc().initWithData_(data)

    def __len__(self):
        return len(self.namelist)

丸ごと。

unrar の存在確認も subprocess でやろうと思ったけど。

Pythonで外部コマンドの存在チェック(`which`的な) – Qiita

self.is_unrar = shutil.which('unrar') != None

こっちのほうがいいや。
早速試すことにしよう。

WinRAR archiver, a powerful tool to process RAR and ZIP files

から macOS 版 rar のダウンロード。
インストーラーも Makefile も無いので手動でパスを通す。
終わったら cbr にしたい画像の入ったディレクトリで以下を。

rar a test.cbr -- *.jpg

直接 CBR ファイルを作れるよ。

イケた。

Python3 自体の知識が 3.2 くらいで止まっているわ。
最新 Python3 必須になるけど、いやそのほうがいいでしょ普通。
たまには公式サイトも見ることにしよう。

PDF to PNG

我がアプリに PDF 表示機能を追加するのは前回のとおり簡単だった。
問題はサムネイル、GtkDrawingArea を配列に入れればいいやと思っていた。

Matrix で表示させたら凄いメモリ食いなうえスクロールが遅過ぎで使えなかった。
GtkFlowBox を破棄したら突っ込んだ GtkDrawingArea も破棄されるのは当然だった。

結局は縮小 GdkPixbuf にするという何のヒネリもない実装になった。
cr.render は背景は何もしないのね、白で塗り潰す必要があった。
細かくは comipoli のソースを見てくれということで。

つまり PDF は結構簡単に画像にできるってことだ。
それなら変換アプリでも作ろうかなと思ったけど。

#!/usr/bin/env python3
'''
    pdf2png.py
'''

import sys, gi, cairo
gi.require_version('Gdk', '3.0')
gi.require_version('Poppler', '0.18')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gio, Gdk, GdkPixbuf, Poppler

if len(sys.argv) < 2:
    print('Usage: python3 {} PDFFILENAME'.format(sys.argv[0]))
    sys.exit()

f = Gio.File.new_for_path(sys.argv[1])
pdf = Poppler.Document.new_from_gfile(f)
l = pdf.get_n_pages()
for i in range(l):
    page = pdf.get_page(i)
    w, h = page.get_size()
    w = round(w)
    h = round(h)
    with cairo.ImageSurface(cairo.Format.ARGB32, w, h) as surface:
        cr = cairo.Context(surface)
        cr.set_source_rgb(1, 1, 1)
        cr.rectangle(0, 0, w, h)
        cr.fill()
        page.render(cr)
        pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)
        pixbuf.savev('{0:03d}.png'.format(i + 1), 'png', ['compression'], ['9'])

おしまい。

たったこれだけだったのでヤメ!
GNOME ってスゲェなぁ、何故使う人少ないんだろう?