很久沒玩 Playwright 了,找了個題目暖身。

講到自己做梗圖,首推「Meme 梗圖倉庫」的「梗圖產生器」,介面好用素材又齊全,基本上你看過的,想得到的梗圖都做得出來。

正常人其實沒什麼必要花時間自己搞,但我想到梗圖產生器的原理基本上就是在素材底圖指定位置放上自訂文字,理論上用 CSS 不難實現,順便再練習用 .NET 呼叫 Playwright 操作 HTML 網頁組裝後存成 png,便成了一個大小適中的日常練習題。這就是本次的計劃,開始!

我選了 Drake 的 No / Yes 迷因圖 當素材底圖,圖片尺寸 600x600,用 display: flex; flex-wrap: wrap; 橫式排四個寬度 50% 的區塊 flex: 1 0 50%,自動折成兩列,則第 2 個跟第 4 個就是填入上下文字的文字容器。文字容器內部用 display: flex; justify-content: center; align-items: center; 讓文字上下左右置中。

至於粗黑框白字的文字特效,我參考這篇如何利用 CSS 製作完美的文字外框,用 -webkit-text-stroke 粗體特效配合 ::before::after 偽元素,控制 z-index 先疊一層 8px 黑邊框文字、再疊一層 1px 白邊框字,黑框字理論上要圓角才好看,但我沒找到用 CSS 實現的方法,為避免邊角太銳利,我套用 filter: drop-shadow(0px 0px 1px black); 加減柔化。

完整網頁範例如下:

<!DOCTYPE html>
<html>

<body>
    <style>
        html,body { margin: 0; padding:0 }
        div.meme {
            background-image: url(meme.png);
            display: flex;
            flex-wrap: wrap;
            width: 600px;
            height: 600px;
            border: 1px solid black;

            div.cell {
                flex: 1 0 50%;
                border-bottom: 1px solid #777;
                box-sizing: border-box;
                display: flex;
                justify-content: center;
                align-items: center;
                font-family: '微軟正黑體';
                height: 300px;
            }

            [data-stroke] {
                display: inline-block;
                margin: 0 16px;
                font-size: 20pt;
                line-height: 145%;
                position: relative;
                white-space: pre-wrap;
                &::before,&::after {
                    content: attr(data-stroke);
                    position: absolute;
                    left: 0;
                    top: 0;
                }
                &::after {
                    z-index: 1;
                    -webkit-text-stroke: 8px #222;
                    filter: drop-shadow(0px 0px 1px black);
                }
                &::before {
                    z-index: 2;
                    color: white;
                    -webkit-text-stroke: 1px white;
                }

            }
        }
    </style>
    <div class="meme">
        <div class="cell"></div>
        <div class="cell">
            <span id="top-word"></span>
        </div>
        <div class="cell"></div>
        <div class="cell">
            <span id="bottom-word"></span>
        </div>
    </div>

    <script>
        function setWord(word, loc) {
            const elem = document.getElementById(loc + "-word");
            elem.innerText = word;
            elem.setAttribute("data-stroke", word);
            return true;
        }
        setWord("上梗圖產生器網站\n一分鐘做完梗圖", "top");
        setWord("CSS + Playwright \n連 Coding 帶寫文\n花兩小時完成", "bottom");
    </script>
</body>

</html>

Playwright 部分相對簡單,都是以前介紹過的技巧

using System.Text.Json;
using Microsoft.Playwright;

public class MemeMaker
{
    static BrowserTypeLaunchOptions browserOptions = new BrowserTypeLaunchOptions
    {
        // TODO: 自動尋找路徑 https://blog.darkthread.net/blog/playwright-app-dep-issue/
        ExecutablePath = @"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
        Headless = true
    };
    public async Task<byte[]> MakeMeme(string workingFolder, string topText, string bottomText)
    {
        using var playwright = await Playwright.CreateAsync();
        await using var browser = await playwright.Chromium.LaunchAsync(browserOptions);
        var page = await browser.NewPageAsync();
        await page.GotoAsync($"file:///{Path.Combine(workingFolder, "template.html")}");
        await page.EvaluateAsync<bool>($"window.setWord({JsonSerializer.Serialize(topText)}, 'top')");
        await page.EvaluateAsync<bool>($"window.setWord({JsonSerializer.Serialize(bottomText)}, 'bottom')");
        var imgBytes = await page.ScreenshotAsync(new PageScreenshotOptions
        {
            Clip = new Clip
            {
                X = 0,
                Y = 0,
                Width = 600,
                Height = 600
            }   
        });
        File.WriteAllBytes(Path.Combine(workingFolder, "meme-result.png"), imgBytes);
        await page.CloseAsync();
        return imgBytes;
    }
}

簡單實測,成功!

var memeMaker = new MemeMaker();
var memeFolder = Path.Combine(builder.Environment.ContentRootPath, "data");
await memeMaker.MakeMeme(memeFolder, 
    "上梗圖產生器網站\n一分鐘做完梗圖", 
    "CSS + Playwright \n連 Coding 帶寫文\n花兩小時完成");

This article explores creating memes with CSS and Playwright for .NET. It uses a HTML template and Playwright to automate meme creation by adding text to a base image. The provided C# code demonstrates using Playwright to manipulate the HTML and capture a screenshot of the final meme.


Comments

# by python路過吾好錯過

from selenium import webdriver from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common.by import By import time import os def make_meme(template_path, output_path, top_text, bottom_text): # 设置Chrome驱动路径 chrome_driver_path = "path/to/chromedriver" # 替换为你的chromedriver路径 service = ChromeService(chrome_driver_path) options = webdriver.ChromeOptions() options.add_argument('--headless') driver = webdriver.Chrome(service=service, options=options) try: # 打开HTML模板 driver.get(f"file://{os.path.abspath(template_path)}") # 设置文字 driver.execute_script(f"setWord('{top_text}', 'top')") driver.execute_script(f"setWord('{bottom_text}', 'bottom')") # 截图保存 screenshot = driver.get_screenshot_as_png() with open(output_path, 'wb') as f: f.write(screenshot) finally: driver.quit() # 使用示例 make_meme('template.html', 'meme-result.png', "上梗圖產生器網站\n一分鐘做完梗圖", "CSS + Playwright \n連 Coding 帶寫文\n花兩小時完成")

Post a comment