2021年1月17日日曜日

秋のご近所散歩

 11月7日のお散歩写真です。

ご近所を散歩しました。


アパートから徒歩2分ぐらいにある公園の木

なんか、つやつやに写りました。

腐ってもデジタル用レンズだけのことはある?



畑の柿の実



武蔵野公園に行ってみました。




武蔵野公園の隣の緑道

Panasonic DMC-GX7, OLYMPUS 40-150mm




2021年1月9日土曜日

GIMP 2.10 でトーンカーブのインポートでエラーになる

 今のPCに切り替えてから、ずっとGIMP はPortable版の2.8を

使っていたのですが、最近になって Portable 版の2.10 を

インストールして使い始めています。

昔 GIMP 2.6 の頃にダウンロードしたトーンカーブを

GIMP Portable 2.10 トーンカーブの「現在の設定をファイル

からインポート」で読み込むと、「〇〇の読み込み中にエラーが発生しました。

 想定していたよりも早くストリームの最後に到達しました。」

というエラーになってしまいました。




で、GIMP Portable 2.10 でエクスポートしたファイルと見比べて、

以下のことに気を付けてインポートできるようになりました。


(1) 改行コードは LF

(2) 最初の行は "# GIMP Curves File" 以外のことを書かない

(3) 数値データ行の最後にスペースを1文字入れる



(1) について

自分で作ったファイルは CR+LF だったので、エラーになりました。


(2) について

# で始まっている行なので、コメント行かと思って、

適当にコメントを書き足していたら、エラーになりました。


(3) について

昔ダウンロードしたファイルが読めなかったのはこれが原因だったようです。


---


GIMP のトーンカーブがダウンロードできるところを

探してみたら、ここが見つかりました。


https://gist.github.com/neo22s/f7d9a1348b4376f8085b1e1387edf9a5


このページの "gimp-curves-tool.settings" と書いてある右側の Raw

ボタンをクリックすると、設定ファイルだけのページに移るので、それを保存します。

GIMP Portable 2.10 だと、このファイルを GimpPortable を

インストールしたディレクトリの

Data\.gimp\filters\GimpCurvesConfig.settings

に上書きすると、トーンカーブが使えるようになります。

【注】トーンカーブの履歴が消えてしまうので、消えると困る場合は上書きしないでください。


設定したトーンカーブを呼び出すには、GIMP Portableで画像ファイルを

開いたところで、メニューの

「色(C)」 → 「トーンカーブ(C)」

を選択し、トーンカーブのダイアログを開きます。





トーンカーブのダイアログの「プリセット」のドロップダウンリストに

ダウンロードしたトーンカーブのプリセットが入っています。

「Cross Process」のトーンカーブは、昔ダウンロードしたものと同様のカーブでした。





トーンカーブを適用すると、このプリセットのドロップダウンリストに

履歴が追加されるようです。

トーンカーブを1つだけエクスポートしたり、不要なトーンカーブを

削除するには、プリセットのドロップダウンリストの右側の

「プリセットの管理」をクリックします。




クリックすると、プリセットに関するメニューが出るので、

エクスポートしたり、削除したりする機能を呼び出せます。




2021年1月2日土曜日

Python+Pillowでミラーエフェクト

最近、お出かけすることが減って、Pythonを勉強しています。
そのPython言語の、Pillowという画像ライブラリを使ったスクリプトです。
Pythonをインストールして、Pillowのライブラリをpipでインストールしておく必要があります。
記事を書いている時点のPythonは3.8です。

前回までは、塗りつぶしした画像やぼかし加工した画像を元の画像とブレンドしていますが、
今回のスクリプトは、元画像を折り返ししたものを元画像と並べるだけなので、
比較的、簡単な処理になっています。

折り返して貼り合わせるだけなので、あんまり面白くないかなぁって思っていましたが、
元の画像によっては、意外と面白い画像が出来上がりました。

以下にスクリプトのコードを書きます。


"""
鏡に映したように左右対称の画像にする
tkinterを使ったGUI版
Usage : コマンドラインから以下のコマンドで起動
    python mirror_gui.py
"""

import os
from PIL import Image, ImageTk, ImageOps
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox

class FormClass:
    """ 
    鏡に映したように加工する画面のフォームを作成するクラス
    """

    def __init__(self, root):
        # インスタンス変数に初期値設定
        self.root = root     # Windowオブジェクト
        self.filepath = ""   # 入力ファイル名
        self.readimg = None  # 入力画像
        self.dst_img = None  # 加工結果画像

        rowno = 0            # 表示位置(行番号)

        # ラベルを作成
        tk.Label(root, text='入力ファイルPATH'
                ).grid(column=0, row=rowno)

        # 入力ファイル名
        self.src_path = tk.StringVar()

        # テキストボックスを作成
        tb_widget = tk.Entry(root, 
                             width=120, 
                             textvariable=self.src_path)
        tb_widget.grid(column=1, row=rowno)

        # 入力ファイル選択ボタンを作成
        btn_widget = tk.Button(root, 
                               text='入力ファイル選択', 
                               command=self.select_file)
        btn_widget.grid(column=2, row=rowno)
        rowno += 1

        # 左位置 スケールの作成
        tk.Label(root, text='左位置(%)'
                ).grid(column=0, row=rowno)

        self.left = tk.DoubleVar()
        left_sc = tk.Scale(
                     root,
                     variable=self.left,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=100,
                     command=self.draw_effect)
        left_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.left.set(0)
        rowno += 1

        # 右位置 スケールの作成
        tk.Label(root, text='右位置(%)'
                ).grid(column=0, row=rowno)

        self.right = tk.DoubleVar()
        right_sc = tk.Scale(
                     root,
                     variable=self.right,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=100,
                     command=self.draw_effect)
        right_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.right.set(50)
        rowno += 1

        # 反転方向 コンボボックスの作成
        tk.Label(root, text='反転方向'
                ).grid(column=0, row=rowno, pady=5)

        self.direct_select = tk.StringVar()
        direct_combo = ttk.Combobox(
                        root,
                        textvariable=self.direct_select)
        direct_combo.grid(column=1, 
                           row=rowno, 
                           pady=5,
                           sticky=(tk.N, tk.W))
        direct_combo['values'] = ('左右', '上下')
        direct_combo.current(0)
        direct_combo.bind("<>", self.draw_effect)
        self.direct_select.set('左右')
        rowno += 1

        # 左右反転 コンボボックスの作成
        tk.Label(root, text='左右反転'
                ).grid(column=0, row=rowno, pady=5)

        self.reverse_select = tk.StringVar()
        reverse_combo = ttk.Combobox(
                        root,
                        textvariable=self.reverse_select)
        reverse_combo.grid(column=1, 
                           row=rowno, 
                           pady=5,
                           sticky=(tk.N, tk.W))
        reverse_combo['values'] = ('右', '左')
        reverse_combo.current(0)
        reverse_combo.bind("<>", self.draw_effect)
        self.reverse_select.set('右')
        rowno += 1

        # ファイル保存ボタンを作成
        savebtn_widget = tk.Button(root, 
                                   text='ファイル保存', 
                                   command=self.show_save_dialog)
        savebtn_widget.grid(column=0, row=rowno,
                            sticky=(tk.N))

        self.CANVAS_WIDTH = 800
        self.CANVAS_HEIGHT = 600

        # 画像を表示するためのキャンバスの作成
        self.canvas = tk.Canvas(self.root, 
                                width=self.CANVAS_WIDTH, 
                                height=self.CANVAS_HEIGHT)
        self.canvas.grid(column=1, row=rowno, columnspan=2)


    # 入力ファイル選択ボタン押下イベント
    def select_file(self):
        # ファイル選択ダイアログを表示
        ftypes = [("All Files", ".*"),
                  ("JPEG Image Files", ".jpg .jpeg"),
                  ("PNG Image Files", ".png")]

        if len(self.filepath) == 0:
            idir = os.path.abspath(os.path.dirname(__file__))
        else:
            idir = os.path.dirname(self.filepath)
        self.filepath = filedialog.askopenfilename(filetypes = ftypes,
                                                   initialdir = idir)
        if len(self.filepath) == 0 :
            return
        self.src_path.set(self.filepath)

        # 画像を読み取り
        self.readimg = Image.open(self.filepath)
        # Exif情報を取り出し
        if 'exif' in self.readimg.info :
            self.exif = self.readimg.info['exif']
        else :
            self.exif = bytes()

        # 加工した画像を表示
        self.draw_effect()

    # 加工した画像を表示する
    def draw_effect(self,val=0):
        if self.readimg is None :
            return

        # 元画像のサイズ
        w,h = self.readimg.size

        if self.direct_select.get() == '左右' :
            # 左右方向に折り返す
            left = w * self.left.get() / 100.0
            right = w * self.right.get() / 100.0
            w_start = int(min(left,right))
            w_end = int(max(left,right))
            w = (w_end - w_start) * 2
            # 元の画像と、折り返した画像を合成する
            im_crop = self.readimg.crop((w_start, 0, w_end, h))
            im_flip = ImageOps.mirror(im_crop)
            # 出力画像の幅か高さが 0 なら return
            if w == 0 or h == 0:
                return
            self.dst_img = Image.new("RGB", (w, h), 0)
            if self.reverse_select.get() == '右':
                self.dst_img.paste(im_crop)
                self.dst_img.paste(im_flip, ((w_end - w_start), 0))
            else :
                self.dst_img.paste(im_flip)
                self.dst_img.paste(im_crop, ((w_end - w_start), 0))
        else :
            # 上下方向に折り返す
            upper = h * self.left.get() / 100.0
            lower = h * self.right.get() / 100.0
            h_start = int(min(upper,lower))
            h_end = int(max(upper,lower))
            h = (h_end - h_start) * 2
            # 元の画像と、折り返した画像を合成する
            im_crop = self.readimg.crop((0, h_start, w, h_end))
            im_flip = ImageOps.flip(im_crop)
            # 出力画像の幅か高さが 0 なら return
            if w == 0 or h == 0:
                return
            self.dst_img = Image.new("RGB", (w, h), 0)
            if self.reverse_select.get() == '右':
                self.dst_img.paste(im_crop)
                self.dst_img.paste(im_flip, (0, (h_end - h_start)))
            else :
                self.dst_img.paste(im_flip)
                self.dst_img.paste(im_crop, (0, (h_end - h_start)))


        # 画像の表示倍率を計算する
        rate = min(1.0, 
                   self.CANVAS_WIDTH / self.dst_img.width, 
                   self.CANVAS_HEIGHT / self.dst_img.height)

        # 表示用に画像を縮小する
        new_w = int(self.dst_img.width * rate)
        new_h = int(self.dst_img.height * rate)
        small_resized = self.dst_img.resize((new_w, new_h))

        # キャンバスに画像を表示する。
        self.im = ImageTk.PhotoImage(image=small_resized)
        self.canvas.create_image(0, 0, anchor=tk.NW, image=self.im)

    # ファイル保存ボタンイベント
    def show_save_dialog(self):
        if self.dst_img is not None :
            ftypes = [("All Files", ".*"),
                      ("JPEG Image Files", ".jpg .jpeg"),
                      ("PNG Image Files", ".png")]
            ini_fname = os.path.basename(self.filepath)
            filename = filedialog.asksaveasfilename(filetypes=ftypes,
                                                    initialfile=ini_fname)
            if filename:
                # 保存先ファイルが指定されたら、加工した画像を保存
                self.dst_img.save(filename, quality=100, exif=self.exif)
        else :
            tk.messagebox.showerror(title="エラー", 
                                    message="入力ファイルを指定してください")

# windowを描画
window = tk.Tk()
# windowサイズを変更
window.geometry("1000x800")
# windowタイトルを設定
window.title("Mirror effect")

# フォームを作成、表示
FormClass(window)

# 画面を操作されるのを待つ
window.mainloop()

上のスクリプトをUTF-8のエンコーディングで、mirror_gui.py というファイルに保存します。
Windowsだとコマンドプロンプト等を開いて、スクリプトを保存したフォルダにcdコマンドで移動して、

python mirror_gui.py

で実行します。 実行すると、



このような画面を表示します。
入力ファイル選択ボタンで、ファイル選択ダイアログを開きます。
そこでファイルを選択すると、



このように画像のプレビューを表示します。

「左位置」を大きくすると、切り出す左側の位置が右に移動します。

「右位置」を大きくすると、切り出す右側の位置が右に移動します。

「反転方向」で、左右反転と上下反転を切り替えます。

「左右反転」で右画像を折り返して左に貼り付けるか、
  左画像を折り返して右に貼り付けるかを選択します。

「ファイル保存ボタン」をクリックすると、保存先選択ダイアログを開きます。


以下、このスクリプトで加工した画像をアップします。



新橋辺り





ガクアジサイ





コオニユリ

2020年12月13日日曜日

Python+Pillowでミニチュア風に上下をぼかす

最近、お出かけすることが減って、Pythonを勉強しています。
そのPython言語の、Pillowという画像ライブラリを使ったスクリプトです。
Pythonをインストールして、Pillowのライブラリをpipでインストールしておく必要があります。
記事を書いている時点のPythonは3.8です。

前回のToycamera風に加工するスクリプトは、黒あるいは白で塗りつぶした画像を
丸いマスクでブレンドしていましたが、今回はぼかした画像を
横長な長方形のマスクでブレンドしています。
今回のスクリプトは、おもちゃっぽくするために、ブレンドした後の画像の彩度を上げて、
メディアンフィルターで質感を変えています。

以下にスクリプトのコードを書きます。


"""
ミニチュア風に上下をぼかす
tkinterを使ったGUI版
Usage : コマンドラインから以下のコマンドで起動
    python miniture_gui.py
"""

import os
from PIL import Image, ImageDraw, ImageFilter, ImageTk, ImageEnhance
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox

class FormClass:
    """ 
    ミニチュア風に加工する画面のフォームを作成するクラス
    """

    def __init__(self, root):
        # インスタンス変数に初期値設定
        self.root = root     # Windowオブジェクト
        self.filepath = ""   # 入力ファイル名
        self.readimg = None  # 入力画像
        self.dst_img = None  # 加工結果画像

        rowno = 0            # 表示位置(行番号)

        # ラベルを作成
        tk.Label(root, text='入力ファイルPATH'
                ).grid(column=0, row=rowno)

        # 入力ファイル名
        self.src_path = tk.StringVar()

        # テキストボックスを作成
        tb_widget = tk.Entry(root, 
                             width=120, 
                             textvariable=self.src_path)
        tb_widget.grid(column=1, row=rowno)

        # 入力ファイル選択ボタンを作成
        btn_widget = tk.Button(root, 
                               text='入力ファイル選択', 
                               command=self.select_file)
        btn_widget.grid(column=2, row=rowno)
        rowno += 1

        # 上位置 スケールの作成
        tk.Label(root, text='上位置(%)'
                ).grid(column=0, row=rowno)

        self.upper = tk.DoubleVar()
        upper_sc = tk.Scale(
                     root,
                     variable=self.upper,
                     orient=tk.HORIZONTAL,
                     from_=0.0,
                     to=100,
                     command=self.draw_effect)
        upper_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.upper.set(40)
        rowno += 1

        # 下位置 スケールの作成
        tk.Label(root, text='下位置(%)'
                ).grid(column=0, row=rowno)

        self.lower = tk.DoubleVar()
        lower_sc = tk.Scale(
                     root,
                     variable=self.lower,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=100.0,
                     command=self.draw_effect)
        lower_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.lower.set(70)
        rowno += 1

        # 境界ぼかし スケールの作成
        tk.Label(root, text='境界ぼかし'
                ).grid(column=0, row=rowno)

        self.blur_radius = tk.IntVar()
        blur_sc = tk.Scale(
                     root,
                     variable=self.blur_radius,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=100,
                     command=self.draw_effect)
        blur_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.blur_radius.set(35)
        rowno += 1

        # 効果の強さ スケールの作成
        tk.Label(root, text='効果の強さ'
                ).grid(column=0, row=rowno)

        self.strength = tk.IntVar()
        strength_sc = tk.Scale(
                     root,
                     variable=self.strength,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=50,
                     command=self.draw_effect)
        strength_sc.grid(column=1, 
                         row=rowno, 
                         sticky=(tk.N, tk.E, tk.S, tk.W))
        self.strength.set(10)
        rowno += 1

        # ファイル保存ボタンを作成
        savebtn_widget = tk.Button(root, 
                                   text='ファイル保存', 
                                   command=self.show_save_dialog)
        savebtn_widget.grid(column=0, row=rowno,
                            sticky=(tk.N))

        self.CANVAS_WIDTH = 800
        self.CANVAS_HEIGHT = 600

        # 画像を表示するためのキャンバスの作成
        self.canvas = tk.Canvas(self.root, 
                                width=self.CANVAS_WIDTH, 
                                height=self.CANVAS_HEIGHT)
        self.canvas.grid(column=1, row=rowno, columnspan=2)


    # 入力ファイル選択ボタン押下イベント
    def select_file(self):
        # ファイル選択ダイアログを表示
        ftypes = [("All Files", ".*"),
                  ("JPEG Image Files", ".jpg .jpeg"),
                  ("PNG Image Files", ".png")]

        if len(self.filepath) == 0:
            idir = os.path.abspath(os.path.dirname(__file__))
        else:
            idir = os.path.dirname(self.filepath)
        self.filepath = filedialog.askopenfilename(filetypes = ftypes,
                                                   initialdir = idir)
        if len(self.filepath) == 0 :
            return
        self.src_path.set(self.filepath)

        # 画像を読み取り
        self.readimg = Image.open(self.filepath)

        # 加工した画像を表示
        self.draw_effect()

    # 加工した画像を表示する
    def draw_effect(self,val=0):
        if self.readimg is None :
            return

        # 円を描画する開始位置、終了位置を計算する
        w,h = self.readimg.size
        upper = h * self.upper.get() / 100.0
        lower = h * self.lower.get() / 100.0
        h_start = int(min(upper,lower))
        h_end = int(max(upper,lower))

        # マスク画像を描画する
        mask = Image.new("L", (w, h), 0)
        draw = ImageDraw.Draw(mask)

        draw.rectangle((0, h_start, w, h_end), fill=255)
        mask_blur = mask.filter(
                        ImageFilter.GaussianBlur(
                            self.blur_radius.get()))

        # ぼかし画像を作成する
        blur_img = self.readimg.filter(ImageFilter.GaussianBlur(self.strength.get()))

        # 元の画像と、塗りつぶし画像を合成する
        self.dst_img = Image.composite(self.readimg, blur_img, mask_blur)

        # 合成した画像の彩度を上げる
        img_enhancer = ImageEnhance.Color(self.dst_img)
        self.dst_img = img_enhancer.enhance(1.5)

        # medianフィルタを適用する
        self.dst_img = self.dst_img.filter(ImageFilter.MedianFilter(3))

        # 画像の表示倍率を計算する
        rate = min(1.0, 
                   self.CANVAS_WIDTH / self.readimg.width, 
                   self.CANVAS_HEIGHT / self.readimg.height)

        # 表示用に画像を縮小する
        new_w = int(self.readimg.width * rate)
        new_h = int(self.readimg.height * rate)
        small_resized = self.dst_img.resize((new_w, new_h))

        # キャンバスに画像を表示する。
        self.im = ImageTk.PhotoImage(image=small_resized)
        self.canvas.create_image(0, 0, anchor=tk.NW, image=self.im)

    # ファイル保存ボタンイベント
    def show_save_dialog(self):
        if self.dst_img is not None :
            ftypes = [("All Files", ".*"),
                      ("JPEG Image Files", ".jpg .jpeg"),
                      ("PNG Image Files", ".png")]
            ini_fname = os.path.basename(self.filepath)
            filename = filedialog.asksaveasfilename(filetypes=ftypes,
                                                    initialfile=ini_fname)
            if filename:
                # 保存先ファイルが指定されたら、加工した画像を保存
                self.dst_img.save(filename, quality=100)
        else :
            tk.messagebox.showerror(title="エラー", 
                                    message="入力ファイルを指定してください")

# windowを描画
window = tk.Tk()
# windowサイズを変更
window.geometry("1000x800")
# windowタイトルを設定
window.title("Miniture effect")

# フォームを作成、表示
FormClass(window)

# 画面を操作されるのを待つ
window.mainloop()

上のスクリプトをUTF-8のエンコーディングで、miniture_gui.py というファイルに保存します。
Windowsだとコマンドプロンプト等を開いて、スクリプトを保存したフォルダにcdコマンドで移動して、

python miniture_gui.py

で実行します。 実行すると、


このような画面を表示します。 入力ファイル選択ボタンで、ファイル選択ダイアログを開きます。 そこでファイルを選択すると、

このように画像のプレビューを表示します。 「上位置」を大きくすると、上側のぼける面積が広くなります。 「下位置」を大きくすると、下側のぼける面積が狭くなります。 「境界ぼかし」を大きくすると、ぼける領域とぼけない領域の境がわかりにくくなります。 「ファイル保存ボタン」をクリックすると、保存先選択ダイアログを開きます。 以下、このスクリプトで加工した画像をアップします。

江の島にて

羽村堰の玉川上水

福生の公園

2020年12月6日日曜日

Python+Pillowでトイカメラ風に周辺光量落ちさせる その2

最近、お出かけすることが減って、Pythonを勉強しています。
そのPython言語の、Pillowという画像ライブラリを使ったスクリプトです。
Pythonをインストールして、Pillowのライブラリをpipでインストールしておく必要があります。
記事を書いている時点のPythonは3.8です。

前回のトイカメラ風に加工するスクリプトを、TkinterというGUIライブラリを使って、
画面で操作できるようにしました。
GUI化することで、効果を確認しながらパラメータを決められるのが良いですが、
何枚も同じ加工を連続して行うのは向きません。

以下にスクリプトのコードを書きます。



"""
トイカメラ風に周辺減光させる 
tkinterを使ったGUI版
Usage : コマンドラインから以下のコマンドで起動
    python toycamera_gui.py
"""

import os
from PIL import Image, ImageDraw, ImageFilter, ImageTk
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox

class FormClass:
    """ 
    Toycamera風に加工する画面のフォームを作成するクラス
    """

    def __init__(self, root):
        # インスタンス変数に初期値設定
        self.root = root     # Windowオブジェクト
        self.readimg = None  # 入力画像
        self.dst_img = None  # 加工結果画像

        rowno = 0            # 表示位置(行番号)

        # ラベルを作成
        tk.Label(root, text='入力ファイルPATH'
                ).grid(column=0, row=rowno)

        # 入力ファイル名
        self.src_path = tk.StringVar()

        # テキストボックスを作成
        tb_widget = tk.Entry(root, 
                             width=120, 
                             textvariable=self.src_path)
        tb_widget.grid(column=1, row=rowno)

        # 入力ファイル選択ボタンを作成
        btn_widget = tk.Button(root, 
                               text='入力ファイル選択', 
                               command=self.select_file)
        btn_widget.grid(column=2, row=rowno)
        rowno += 1

        # 塗り残す範囲の比率 スケールの作成
        tk.Label(root, text='塗り残す範囲の比率(%)'
                ).grid(column=0, row=rowno)

        self.rate = tk.DoubleVar()
        rate_sc = tk.Scale(
                     root,
                     variable=self.rate,
                     orient=tk.HORIZONTAL,
                     from_=0.0,
                     to=100,
                     command=self.draw_effect)
        rate_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.rate.set(40)
        rowno += 1

        # ぼかし半径 スケールの作成
        tk.Label(root, text='ぼかし半径'
                ).grid(column=0, row=rowno)

        self.blur_radius = tk.IntVar()
        blur_sc = tk.Scale(
                     root,
                     variable=self.blur_radius,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=40.0,
                     command=self.draw_effect)
        blur_sc.grid(column=1, 
                     row=rowno, 
                     sticky=(tk.N, tk.E, tk.S, tk.W))
        self.blur_radius.set(20)
        rowno += 1

        # 効果の強さ スケールの作成
        tk.Label(root, text='効果の強さ'
                ).grid(column=0, row=rowno)

        self.strength = tk.IntVar()
        strength_sc = tk.Scale(
                     root,
                     variable=self.strength,
                     orient=tk.HORIZONTAL,
                     from_=0,
                     to=255,
                     command=self.draw_effect)
        strength_sc.grid(column=1, 
                         row=rowno, 
                         sticky=(tk.N, tk.E, tk.S, tk.W))
        self.strength.set(80)
        rowno += 1

        # 塗りつぶし色 コンボボックスの作成
        tk.Label(root, text='塗りつぶし色'
                ).grid(column=0, row=rowno)

        self.select_color = tk.StringVar()
        color_combo = ttk.Combobox(
                     root,
                     textvariable=self.select_color)
        color_combo.grid(column=1, 
                         row=rowno, 
                         sticky=(tk.N, tk.W))
        color_combo['values'] = ('白', '黒')
        color_combo.current(1)
        color_combo.bind("<>", self.draw_effect)
        rowno += 1

        # ファイル保存ボタンを作成
        savebtn_widget = tk.Button(root, 
                                   text='ファイル保存', 
                                   command=self.show_save_dialog)
        savebtn_widget.grid(column=0, row=rowno,
                            sticky=(tk.N))

        self.CANVAS_WIDTH = 800
        self.CANVAS_HEIGHT = 600

        # 画像を表示するためのキャンバスの作成
        self.canvas = tk.Canvas(self.root, 
                                width=self.CANVAS_WIDTH, 
                                height=self.CANVAS_HEIGHT)
        self.canvas.grid(column=1, row=rowno, columnspan=2)

    # 入力ファイル選択ボタン押下イベント
    def select_file(self):
        # ファイル選択ダイアログを表示
        ftypes = [("All Files", ".*"),
                  ("JPEG Image Files", ".jpg .jpeg"),
                  ("PNG Image Files", ".png")]

        filepath = self.src_path.get()
        if len(filepath) == 0:
            idir = os.path.abspath(os.path.dirname(__file__))
        else:
            idir = os.path.dirname(filepath)
        filepath = filedialog.askopenfilename(filetypes = ftypes,
                                              initialdir = idir)
        if len(filepath) == 0 :
            return
        self.src_path.set(filepath)

        # 画像を読み取り
        self.readimg = Image.open(filepath)

        # 加工した画像を表示
        self.draw_effect()

    # 加工した画像を表示する
    def draw_effect(self,val=0):
        if self.readimg is None :
            return

        # 塗りつぶし色
        fill_color = None
        if self.select_color.get() == '白' :
            fill_color = (255, 255, 255)
        else :
            # 黒く塗りつぶす場合
            fill_color = (0, 0, 0)

        # 円を描画する開始位置、終了位置を計算する
        w,h = self.readimg.size
        radius = max(w,h) * self.rate.get() / 100.0
        w_start = (w / 2) - radius
        w_end = (w / 2) + radius
        h_start = (h / 2) - radius
        h_end = (h / 2) + radius

        # マスク画像を描画する
        mask = Image.new("L", (w, h), 255-self.strength.get())
        draw = ImageDraw.Draw(mask)
        draw.ellipse((w_start, h_start, w_end, h_end), fill=255)
        mask_blur = mask.filter(
                        ImageFilter.GaussianBlur(
                            self.blur_radius.get()))

        # 塗りつぶし画像を作成する
        plane = Image.new("RGB", (w, h), fill_color)

        # 元の画像と、塗りつぶし画像を合成する
        self.dst_img = Image.composite(self.readimg, plane, mask_blur)

        # 画像の表示倍率を計算する
        rate = min(1.0, 
                   self.CANVAS_WIDTH / self.readimg.width, 
                   self.CANVAS_HEIGHT / self.readimg.height)

        # 表示用に画像を縮小する
        new_w = int(self.readimg.width * rate)
        new_h = int(self.readimg.height * rate)
        small_resized = self.dst_img.resize((new_w, new_h))

        # キャンバスに画像を表示する。
        self.im = ImageTk.PhotoImage(image=small_resized)
        self.canvas.create_image(0, 0, anchor=tk.NW, image=self.im)

    # ファイル保存ボタンイベント
    def show_save_dialog(self):
        if self.dst_img is not None :
            ftypes = [("All Files", ".*"),
                      ("JPEG Image Files", ".jpg .jpeg"),
                      ("PNG Image Files", ".png")]

            ini_fname = os.path.basename(self.src_path.get())
            filename = filedialog.asksaveasfilename(filetypes=ftypes,
                                                    initialfile=ini_fname)
            if filename:
                # 保存先ファイルが指定されたら、加工した画像を保存
                self.dst_img.save(filename, quality=100)
        else :
            tk.messagebox.showerror(title="エラー", 
                                    message="入力ファイルを指定してください")

# windowを描画
window = tk.Tk()
# windowサイズを変更
window.geometry("1000x800")
# windowタイトルを設定
window.title("Toycamera effect")

# フォームを作成、表示
FormClass(window)

# 画面を操作されるのを待つ
window.mainloop()


上のスクリプトをUTF-8のエンコーディングで、toycamera_gui.py というファイルに保存します。
Windowsだとコマンドプロンプト等を開いて、スクリプトを保存したフォルダにcdコマンドで移動して、

python toycamera_gui.py

で実行します。 実行すると、



このような画面を表示します。
入力ファイル選択ボタンで、ファイル選択ダイアログを開きます。
そこでファイルを選択すると、




このように画像のプレビューを表示します。

「塗り残す範囲の比率」を大きくすると、塗りつぶしする面積が狭くなります。

「ぼかし半径」を大きくすると、塗りつぶしの境界がぼけます。

「効果の強さ」を大きくすると、塗りつぶしがはっきりします。

「塗りつぶし色」を白にすると、周辺を白く塗りつぶしします。

「ファイル保存ボタン」をクリックすると、保存先選択ダイアログを開きます。


塗りつぶしを白に変更した例



2020年11月29日日曜日

Python+Pillowでトイカメラ風に周辺光量落ちさせる その1

最近、お出かけすることが減って、Pythonを勉強しています。
そのPython言語の、Pillowという画像ライブラリを使ったスクリプトです。
Pythonをインストールして、Pillowのライブラリをpipでインストールして
おく必要があります。
記事を書いている時点のPythonは3.8です。

前回までは、描画系のスクリプトを作りましたが、
今回から写真加工するスクリプトです。

【注意】
ファイルを書き出すスクリプトなので、
消えて困るファイルのあるフォルダでは実行しないでください。

以下にスクリプトのコードを書きます。


"""
トイカメラ風に周辺減光させる
Usage :
    python toycamera.py 入力画像 出力画像 塗り残す比率 ぼかし半径
起動パラメータ
    塗り残す比率 : 0.3 から 0.5 程度
    ぼかし半径 : 20 から 50 程度
             大きくすると塗りつぶしの境界がなだらかな調子になります
"""

import os
import sys
from PIL import Image, ImageDraw, ImageFilter

# 起動パラメータ数チェック
if len(sys.argv) < 3 :
    print("usage : python toycamera.py 入力ファイル 出力ファイル  塗り残す比率 ぼかし半径")
    sys.exit()

# 塗り残す範囲の比率
if len(sys.argv) > 3 :
    r_rate = float(sys.argv[3])
else:
    r_rate = 0.5
# ぼかし半径
if len(sys.argv) > 4 :
    blur_radius = int(sys.argv[4])
else:
    blur_radius = 50

if os.path.isfile(sys.argv[1]) is False :
    print("入力ファイル名にファイルではない名前が指定されています。")
    print("実行を中止します。")
    sys.exit()

if os.path.isdir(sys.argv[2]):
    print("出力ファイル名にディレクトリ名が指定されています。")
    print("実行を中止します。")
    sys.exit()

STRENGTH = 120               # 効果の強さ 0(最強)~255(効果なし)

print("以下の条件で処理を行います。")
print(f"入力ファイル {sys.argv[1]}")
print(f"出力ファイル {sys.argv[2]}")
print(f"塗り残す比率 {r_rate}")
print(f"ぼかし半径   {blur_radius}")
print(f"効果の強さ   {STRENGTH}")

inkey = input("よろしいですか?")
if inkey not in ("y", "Y") :
    print("実行を中止します。")
    sys.exit()

if os.path.exists(sys.argv[2]):
    print("\n出力ファイルが存在します。")
    inkey = input("上書きしてよろしいですか?")
    if inkey not in ("y", "Y") :
        print("実行を中止します。")
        sys.exit()

# 塗りつぶし色
# 白く塗りつぶす場合
#fill_color = (255, 255, 255)
# 黒く塗りつぶす場合
fill_color = (0, 0, 0)

# 画像を読み取り
im = Image.open(sys.argv[1])

# 円を描画する開始位置、終了位置を計算する
w,h = im.size
radius = max(w,h) * r_rate
w_start = (w / 2) - radius
w_end = (w / 2) + radius
h_start = (h / 2) - radius
h_end = (h / 2) + radius

# マスク画像を描画する
mask = Image.new("L", (w, h), STRENGTH)
draw = ImageDraw.Draw(mask)
draw.ellipse((w_start, h_start, w_end, h_end), fill=255)
mask_blur = mask.filter(ImageFilter.GaussianBlur(blur_radius))

# 塗りつぶし画像を作成する
plane = Image.new("RGB", (w, h), fill_color)

# 元の画像と、塗りつぶし画像を合成する
im2 = Image.composite(im, plane, mask_blur)
# 画像を表示
im2.show()
im2.save(sys.argv[2], quality=100)


上のスクリプトをUTF-8のエンコーディングで、toycamera.py という
ファイルに保存します。
Windowsだとコマンドプロンプト等を開いて、スクリプトを保存した


フォルダにcdコマンドで移動して、

python toycamera.py 入力画像名 出力画像名 塗り残す比率 ぼかし半径

で実行します。 実行すると、

  以下の条件で処理を行います。
    (中略)
  よろしいですか?

という確認メッセージを表示するので、
Y をキー入力すると、ファイルを加工して出力します。

入力画像の例






出力画像の例



この例は、
塗り残す比率 : 0.4
ぼかし半径 : 40
で作成しました。

スクリプト内の、 STRENGTH を変更すると塗りつぶしの強さが変わります。
また、 fill_color を (0,0,0) から (255,255,255) に変更すると、
周辺を白く加工します。



金曜日の夕方に、近所のハードオフで、Washburn XB200という
エントリクラスのベースギターのジャンク品を\5500で購入しました。

先週の3連休に見つけたものですが、トーンの可変抵抗の
ナットが無く、ボディの中に落ちてしまっているため
ジャンク品ということでした。
お店にチューナーを持っていって、チューニングしてみたところ、
ブリッジを目いっぱい下げているのに、弦の高さがすごく高くて
その時は、購入を見送りました。

でも、持った感じが軽くて弾きやすそうな感じがしたし、
ネック自体はまっすくだったし、
ネックの裏側がグロス仕上げで高級感があったので、
気になっていました。
あと、ブリッジがGOTOH製のものが付いていて、
最悪、部品取りすれば、丸損にはならないというのもあります。

弦高の問題は、ネックとボディのジョイント部分にシムを
入れたら、治るのではと気がついたので、購入に至りました。

金曜の夕方に買って、表面を掃除して、ジョイントを
外すと、メーカーで入れたと思われるシムが既に入っていたの
ですが、ボディ側の木材がシムの形に凹んでいて、
シムを入れた意味が無くなってしまっていました。

手元にあった厚紙を3枚重ねてネックとボディの間に入れたら、
弦高が正常になったのですが、そこに至るまでに、
厚紙の枚数を変えてジョイントのネジを締めて
チューニングを合わせて・・・というのを
4回ぐらいやりました。

あとは、弦、ブリッジ、ボリューム、つまみ、ピックアップを止めるビス、
ピックアップ用クッション、ジャックを交換し、ペグがぐらつくので
グリスを少し入れて現在に至ります。

このベース、前ピックアップと後ピックアップ用に
ボリュームがあるのに、バランス用の可変抵抗も
付いているのが珍しいです。
バランス用の可変抵抗にコンデンサも付いているので、
ハイパスのトーンかと思ったのですが、使ってみると
PUバランスだったという。

2020年11月22日日曜日

Python+PillowでRGBスケールを描画する

 最近、お出かけすることが減って、Pythonを勉強しています。

そのPython言語の、Pillowという画像ライブラリを使ったスクリプトです。

Pythonをインストールして、Pillowのライブラリをpipでインストールしておく

必要があります。

記事を書いている時点のPythonは3.8です。


前回はいろいろな大きさの円形をランダムな位置に描画するスクリプトを作りました、

今回は赤、緑、青、灰色の色スケールを描画するスクリプトです。


以下にスクリプトのコードを書きます。




"""
PillowでRGBスケールを描画する
"""

import random
from PIL import Image, ImageDraw

def main():
    """
    画像作成処理
    """

    BLOCK_WIDTH = 100                  # ブロックの幅
    BLOCK_GAP = 5                      # ブロック間の幅
    IMG_WIDTH = 4*BLOCK_WIDTH + 3*BLOCK_GAP  # 画像の幅
    BLOCK_HEIGHT = 32                   # ブロックの高さ
    BLOCK_STEP = 16                     # ブロックの色の増分
    IMG_HEIGHT = int(BLOCK_HEIGHT*(256/BLOCK_STEP+1))  # 画像の高さ
    BACKGROUND = (255, 255, 255)       # 背景色

    # 画像を作成
    im = Image.new("RGB", (IMG_WIDTH, IMG_HEIGHT), BACKGROUND)
    draw = ImageDraw.Draw(im)

    # 4つの色で繰り返し描画する
    pos_x_st = 0
    for i in range(4):
        if i > 0 :
            pos_x_st = pos_x_st + BLOCK_WIDTH + BLOCK_GAP
        pos_x_en = pos_x_st + BLOCK_WIDTH

        # 色ごとに明るさを変えて描画する
        light = 0
        r = 0
        g = 0
        b = 0
        for j in range(int(256 / BLOCK_STEP + 1)):
            pos_y_st = j * BLOCK_HEIGHT
            pos_y_en = (j+1) * BLOCK_HEIGHT

            # 各色 36から255の乱数を生成
            if i == 0 :
                # 赤スケール
                r = light
            elif i == 1 :
                # 緑スケール
                g = light
            elif i == 2 :
                # 青スケール
                b = light
            else :
                # 白黒スケール
                r = light
                g = light
                b = light

            # 1つのブロックを描画する
            draw.rectangle((pos_x_st, pos_y_st, pos_x_en, pos_y_en), (r,g,b))

            # 次のブロックの明るさを計算する
            light = light + BLOCK_STEP
            if light > 255 :
                light = 255

    # 画像を保存
    im.save("rgb_scale.png")

    # 画像を表示
    im.show()

if __name__ == '__main__':
    # 画像作成処理を実行
    main()




上のスクリプトをUTF-8のエンコーディングで、拡張子 .py のファイルにして保存します。

Windowsだとコマンドプロンプト等を開いて、スクリプトを保存したフォルダにcdコマンドで移動して、


python スクリプトファイル名


で実行します。 実行すると、



こんな感じの画像が表示されると思います。


スクリプトの中の、BLOCK_HEIGHT を 32 から 4 に、

BLOCK_STEP を 16 から 2 に変更して、実行すると、

こんな感じの画像になります。



出力画像の大きさが少し変わってしまうのは、ご愛敬ということで。


まじめにディスプレイの調整をしたいのであれば、

https://www.eizo.co.jp/eizolibrary/other/itmedia04/

こちらのページから、いろいろな確認用の画像が入手できます。