PyObjC」タグアーカイブ

Create 72dpi Icon in Retina Display

JXA では手抜きをしたけど今回はキチンと ICON を作ろう。

icnsファイルの作り方(Mac) – 2番煎じMEMO

こんなに沢山の画像を作るなんて面倒だよ。
これも PyObjC で一つの画像からリサイズでやってしまおう。

画像のリサイズ保存方法は検索で簡単に見つかる。
しかし、Retina Display の mac では勝手に 144dpi になってしまう!
逆にそれを利用して 144dpi を作る、72dpi を自力で作る手段を探す。

NSImage をリサイズする。

なるほど、丸パクさせていただきます。
PyObjC では下記のように。

API Notes: Quartz frameworks ? PyObjC ? the Python ? Objective-C bridge

ということで。

#!/usr/bin/env python3

# make_icns.py
# This Program is Retina Display Mac Only

import os
from AppKit import *
from Quartz.CoreGraphics import *

# Preference
PNGFILE = 'icon.png'
ICONSET = 'comipoli.iconset'

src_image = NSImage.alloc().initWithContentsOfFile_(PNGFILE)
os.mkdir(ICONSET)
os.chdir(ICONSET)

def create_png(img, name):
    bmp = NSBitmapImageRep.imageRepWithData_(img.TIFFRepresentation())
    data = bmp.representationUsingType_properties_(NSBitmapImageFileTypePNG, {})
    data.writeToFile_atomically_(name, True)


for n in [512, 256, 128, 32, 16]:
    # 72dpi
    img = NSBitmapImageRep.imageRepWithData_(src_image.TIFFRepresentation()).CGImage()
    ctx = CGBitmapContextCreate(None, n, n, 8, 4 * n, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast)
    CGContextDrawImage(ctx, CGRectMake(0, 0, n, n), img)
    imgref = CGBitmapContextCreateImage(ctx)
    image72dpi = NSImage.alloc().initWithCGImage_size_(imgref, (n,n))
    create_png(image72dpi, 'icon_{0}x{0}.png'.format(n))
    # 144dpi
    image144dpi = NSImage.alloc().initWithSize_(NSMakeSize(n, n))
    image144dpi.lockFocus()
    NSGraphicsContext.saveGraphicsState()
    NSGraphicsContext.currentContext().setImageInterpolation_(NSImageInterpolationHigh)
    src_image.drawInRect_(NSMakeRect(0, 0, n, n))
    NSGraphicsContext.restoreGraphicsState()
    image144dpi.unlockFocus()
    create_png(image144dpi, 'icon_{0}x{0}@2x.png'.format(n))

で。

よし使える。

実はもう app 化は完成しているんだけど。
app にすると unrar にパスを通しても unrar を認識しない問題が出た。
そりゃ app は bashrc なんて読み込みしないよな、本来 macOS には無いし。

cancelOperation

OS Xアプリケーションにおける環境設定ウィンドウの作り方 ? Genji App Blog

esc でウインドウを閉じるには cancelOperation を処理するだけなのか。
でも書いてあるとおり macOS でこの動作は一時的なウインドウのみ。

オリジナルは Eye of GNOME に合わせて esc で終了できるようにしているけど。
macOS 版では本体を閉じる設定は無しにしたほうがいいようだ。
設定ウインドウやサムネイルウインドウに適用することに。

それから、macOS ってスピンボタンが全然使われていないよね。
NSStepper というものが一応あるんだけど、どのアプリも使っていない。
macOS っぽいものにしたいのでサムネイルサイズ設定は NSSlider にする。
80-200 の 20px ステップで問題無いだろう。

それから、macOS アプリの設定はどれも全体設定になっている。
つまりグローバル変数にする必要がある。
どうせなら NSApp にくっつけときたいよね、ということで。

class ComipoliApplication(NSApplication):
    # global variable
    init_frame = None
    esc_close = False
    is_pdf = False
    thumbnail_height = 120
    is_unrar = False
    is_7za = False

def main():
    #NSApplication.sharedApplication()
    ComipoliApplication.sharedApplication()

サブクラスにしちゃえです。

それと macOS でマウスを使っている人はいない、と思う。
マウスカーソルを変更してクリックでページめくりもいらないな。

設定は二つだけになってもーた。
そんなこんなで、こいつでやりたかった機能は全部付いた。
PyObjC の参考にでも使ってね。
comipoli_pyobjc_3.tar.gz

後は app 化。
py2app ってのでイケるらしいけど分割ファイルでの手段が見付からない。
たいした行数ではないので一つのソースにまとめてもいいんだけど。

PyObjC NSWindow Cascade Position

macOS では二つ目以降のウインドウは規則的にズレて表示される。
でも NSWindow は NSRect を指定して作成する、どうやっているのか。

cascadeTopLeftFromPoint: – Cocoa API解説(macOS/iOS)

そういうことか、解説ありがとう。
window を作った後に自分で移動するのね。

objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
    frame,
    NSTitledWindowMask |
    NSClosableWindowMask |
    NSResizableWindowMask |
    NSMiniaturizableWindowMask,
    NSBackingStoreBuffered,
    False)
# Cascade Position
if len(NSApp.windows()) > 1:
    point = NSApp.keyWindow().cascadeTopLeftFromPoint_(NSZeroPoint)
    self.setFrameTopLeftPoint_(point)

で。

うん macOS アプリっぽくなった。
次は設定ダイアログの位置決めをどうするかだ。
小ネタでした。

PyObjC NSAttributedString

NSCollectionView は色々カスタムできるようなのでとやってみたけど。。。。。
いくら探してもチュートリアル Interface Builder しか出てこない。
しかもボコボコ落ちる、無意味なクラッシュレポートを Apple に沢山送ったよ。
もうあきらめて普通の NSView として使うことにするよ。

ということで。
NSView に文字列を表示したい。

[Objective-C]NSAttributedStringの背景色あたりの話|杏z 学習帳(旧)

NSMutableAttributedString というトンデモネェものがあるようだ。
筆者の用途では継承元の NSAttributedString で十分そう。

てか UIColor なの?
PyObjC には UIKit は無いぞ、あってどうするという話だが。
NSColor で当然のようにイケた、これは Apple の解説が悪いな。

comipoli オリジナルのサムネイル文字列は赤、背景色は 66ffff で 50% 半透明。
これを再現するには。

colorWithSRGBRed:green:blue:alpha: – NSColor | Apple Developer Documentation

最大値を 1.0 にして RGBA 指定でイケるのね。
やってみる。

class ItemView(NSView):
    def init(self):
        objc.super(ItemView, self).init()
        self.image = None
        self.num = 0
        return self

    def drawRect_(self, rect):
        NSColor.blackColor().set()
        NSRectFill(rect)
        if (self.image):
            self.image.drawInRect_(rect)
            bg_color = NSColor.colorWithSRGBRed_green_blue_alpha_(0.4, 1.0, 1.0, 0.5)
            text = NSAttributedString.alloc().initWithString_attributes_(
                '{}'.format(self.num + 1),
                {
                    NSForegroundColorAttributeName: NSColor.redColor(),
                    NSBackgroundColorAttributeName: bg_color
                })
            x = rect.size.width / 2
            text.drawAtPoint_(NSMakePoint(x, 0))

オリジナル

よし完成、といいたいけどまだ超不安定。

PyObjC Background Thread

サムネイル画像を作るバックグラウンド処理。

Objective-C で NSThread および GCD で非同期処理した結果を UI に反映する処理を書いてみた – 凹みTips

こんなアホみたく簡単にバックグラウンドスレッドが動かせるのかい!
performSelectorInBackground:withObject:
を使うだけ、GCD は Python なので関係ない。

それから GUI の常識、関数を抜けるまで画像は表示されない。
普通に書くとすべての画像の読み込みが終わってから一気に表示される。
順次表示するには for 文の中で一つ読み終わる毎に関数を抜ける必要がある。

EZ-NET: UI 関連の機能はメインスレッドで実行すること : Objective-C プログラミング

バックグラウンドスレッドの中で
performSelectorOnMainThread:withObject:waitUntilDone:
を使い UniilDone を True にすればメインスレッドの関数を抜けることができる。

Objective-c スゲェ。
みんな大好き yield を使おうと思ったけど必要ないぞ。

残念なのは NSCollectionView.content が NSArray だった。
python の配列のような可変長ではないので丸ごと配列を入れ替えすることに。

#!/usr/bin/env python3

import objc, os, re
from AppKit import *

PATH = '/Users/sasakima-nao/Pictures/nae'

class ItemView(NSView):
    def init(self):
        objc.super(ItemView, self).init()
        self.image = None
        return self

    def drawRect_(self, rect):
        self.image.drawInRect_(rect)

class CollectionViewItem(NSCollectionViewItem):
    def init(self):
        objc.super(CollectionViewItem, self).init()
        self.setView_(ItemView.alloc().initWithFrame_(NSMakeRect(0, 0, 100, 100)))
        return self

    def setRepresentedObject_(self, rep):
        objc.super(CollectionViewItem, self).setRepresentedObject_(rep)
        if rep != None:
            self.view().image = rep['pic']

class ComipoliCollectionView(NSCollectionView):
    def init(self):
        objc.super(ComipoliCollectionView, self).init()
        self.pixs = []
        self.setItemPrototype_(CollectionViewItem.new())
        self.performSelectorInBackground_withObject_(self.readImage_, PATH)
        return self

    def readImage_(self, path):
        l = os.listdir(path)
        for s in l:
            if re.search(r'\.(jpg|jpeg|png|gif)$', s, re.I):
                f = '{0}/{1}'.format(PATH, s)
                image = NSImage.alloc().initWithContentsOfFile_(f)
                self.performSelectorOnMainThread_withObject_waitUntilDone_(self.addImage_, image, True)

    def addImage_(self, image):
        self.pixs.append({'pic': image})
        self.setContent_(self.pixs)

class ComipoliWindow(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 400, 600)
        objc.super(ComipoliWindow, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('thumbnail')
        self.setDelegate_(self)
        # view
        self.canvas = ComipoliCollectionView.new()
        self.canvas.setFrameSize_(frame.size)
        self.contentView().addSubview_(self.canvas)
        return self

    def windowDidResize_(self, sender):
        aw = self.contentView().frame().size.width
        ah = self.contentView().frame().size.height
        size = NSMakeSize(aw, ah)
        self.canvas.setFrameSize_(size)

class ComipoliMenu(NSMenu):
    def init(self):
        objc.super(ComipoliMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
window = ComipoliWindow.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(ComipoliMenu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

前回のに追加。

ウインドウが表示された後に順次画像が表示されていくのが確認できる。