もったかぶった

プログラミングをしたりしなかったり

おしゃれに紙を並べたくて

要約

↓こういうのを作るスクリプト作りました。 f:id:TmottaT:20200617015118p:plain

github.com

挨拶

こんにちはたもったです。実に2か月ぶりの更新です。
時が流れるのは早いもので、ブログ開設当初はこれからビシビシ記事を上げていくぞと思っていたのに、気づけばAPEXを始めたりボードゲームアリーナにはまったりしてありえん時間が経っていました。誰かAPEXの勝ち方を教えてください。

本文

先日ヒカテクさん(http://hikatech.com)と話していて、紙をおしゃれに並べたいねという話になりました。これだけ聞くとなんのこっちゃという話なので下の画像をご覧ください。

f:id:TmottaT:20200617010702j:plain
f:id:TmottaT:20200617011722j:plain
並んでいるだけなのになんかオシャレ…

上の画像のように、紙を縦横綺麗に並べて斜め視点から撮影するとグッとおしゃれになります。
引用元はビズメイト株式会社様と有限会社クローバー様のサイトです。両社とも僕とは全く関係ありませんが、素晴らしい並べっぷりでしたので引用させていただきました。 www.bizmates.jp

www.428.co.jp

というわけで皆さんも自分の文書をおしゃれに並べてみたくなってきましたね。
本記事はそんなおしゃれ紙並べ画像を自動で生成するスクリプトの実装説明です。

ソースコード全文はこちら。

github.com

環境構築

OS:Windows10
使用言語:Python3

モジュール:

  • PIL

  • PyOpenGL

  • FreeGLUT

  • pdf2image

コード解説

PDFを画像にする前編と画像に画角をつけておしゃれにする後編に分けます。今回は前編です。

今回の実装では下の流れで画像を生成します。

  1. PDFの各ページを画像に変換する

  2. 各ページ画像に影を付ける

  3. 影付きの画像を縦横並べる

  4. 縦横並べた画像を斜めから撮影しておしゃれ画像にする。

1.PDFの各ページを画像に変換する

in_pdfというフォルダから任意のPDFを拾ってきてout_imgというフォルダにパコパコ保存していきます。
もちろんin_pdfの中にPDFファイルが入っていないと動きません。out_imgはなければ自動で生成してくれます。

    pdf_dir = pathlib.Path('in_pdf')
    if not pdf_dir.exists():
        pdf_dir.mkdir()
        
    pdf_file = pathlib.Path('in_pdf/' + pdf)
    img_dir = pathlib.Path('out_img')
    if not img_dir.exists():
        img_dir.mkdir()

    # PDFを画像に変換
    base = pdf_file.stem
    images = pdf2image.convert_from_path(pdf_file, grayscale=True, size=1800)
    for index, image in enumerate(images):
        image.save(img_dir/pathlib.Path(base + '-{}.png'.format(index + 1)), 'png')

f:id:TmottaT:20200618205002p:plain
PDFから画像を生成

2.各ページ画像に影を付ける

画像を並べた時にリアリティを出すため、画像に影を付けました。makeShadowって調べたら出てきた関数を利用させてもらっています。
画像の背景の色味と影の色味を合わせる部分を足したのですが、そこがむちゃくちゃ遅いです。誰か改善して。

def makeShadow(image, iterations, border, offset, backgroundColour, shadowColour):
    # image: base image to give a drop shadow
    # iterations: number of times to apply the blur filter to the shadow
    # border: border to give the image to leave space for the shadow
    # offset: offset of the shadow as [x,y]
    # backgroundCOlour: colour of the background
    # shadowColour: colour of the drop shadow
    
    #Calculate the size of the shadow's image
    fullWidth  = image.size[0] + abs(offset[0]) + 2*border
    fullHeight = image.size[1] + abs(offset[1]) + 2*border
    
    #Create the shadow's image. Match the parent image's mode.
    shadow = Image.new("L", (fullWidth, fullHeight), backgroundColour)
    print('\rSaving...make Shadow Image',end='')

    # Place the shadow, with the required offset
    shadowLeft = border + max(offset[0], 0) #if <0, push the rest of the image right
    shadowTop  = border + max(offset[1], 0) #if <0, push the rest of the image down
    #Paste in the constant colour
    shadow.paste(shadowColour, 
                [shadowLeft, shadowTop,
                 shadowLeft + image.size[0],
                 shadowTop  + image.size[1] ])
    
    # Apply the BLUR filter repeatedly
    for i in range(iterations):
        shadow = shadow.filter(ImageFilter.BLUR)
        print('\rSaving...ImageFilter.BLUR ' + str(i) + ' / ' + str(iterations),end='')

    # ここだけ追加。元のままだと影色と背景色が上手くなじまなかったのでshadowの色みをいじった。ココが遅い。
    if shadow.mode != "RGB":
        shadow=shadow.convert("RGB")
    w,h = shadow.size
    print('\rSaving...Set color...\033[K',end='')
    for x in range(w):
        for y in range(h):
            r,g,b=shadow.getpixel((x,y))
            # ここで色味いじり中、白色と設定したい背景色の差分を引くので注意
            shadow.putpixel((x,y), (r-77, g-40, b-14))
        print('\rSaving...Set color... ' + str(x) + ' / ' + str(w),end='')

    # Paste the original image on top of the shadow 
    imgLeft = border - min(offset[0], 0) #if the shadow offset was <0, push right
    imgTop  = border - min(offset[1], 0) #if the shadow offset was <0, push down
    shadow.paste(image, (imgLeft, imgTop))

    return shadow

f:id:TmottaT:20200618205100p:plain
影を付ける

3.影付きの画像を縦横並べる

これは2次元リストになっている画像群を縦横良い感じに並べる関数があるのでそれを利用しました。

def convert_1d_to_2d(l, cols):
    return [l[i:i + cols] for i in range(0, len(l), cols)]

# 2次元リストの画像から
def concat_tile(im_list_2d):
    return cv2.vconcat([cv2.hconcat(im_list_h) for im_list_h in im_list_2d])

def makeTileImage(base, images):
    imgs = []
    for index, image in enumerate(images):
        img = cv2.imread('out_img/shadow/' + base + '_shadow-{}.png'.format(index + 1))
        imgs.append(img)

    result = convert_1d_to_2d(imgs, 6)
    im_tile = concat_tile(result)

f:id:TmottaT:20200618205204p:plain
画像を並べる

今回はここまでです。