streamlitのst.markdown()に画像を埋め込みたい


# 目次
[1. はじめに](#1-はじめに)
[2. やりたいこと](#2-やりたいこと)
[3. [失敗]普通のHTMLの記述方法で試してみる](#3-失敗普通のhtmlの記述方法で試してみる)
[4. [成功]streamlitのバージョンが1.18以上の場合](#4-成功streamlitのバージョンが118以上の場合)
[5. [成功]streamlitのバージョンが1.18未満の場合](#5-成功streamlitのバージョンが118未満の場合)
[6. おわりに](#6-おわりに)

# 1. はじめに
streamlitというライブラリを利用するとPythonで簡単にWebアプリが開発できます。

:::note info
streamlitについてはこちらの記事で説明しています。
[streamlitを使ったお手軽Webアプリ開発](https://qiita.com/sypn/items/80962d84126be4092d3c)
:::

streamlitで作成したWebアプリのデザインを変更し、ページの見栄えを良くしたい場合、st.markdown()というモジュールを利用してHTMLを埋め込み、CSSスタイルを適用するという方法があります。

例えば、通常のstreamlitで"Hello World!"を出力する場合は以下のようなコードを書きます。


```python:app.py
import streamlit as st

# "Hello World!"を表示
st.text("Hello World!")
```
このコードを実行して表示されるページは以下の画像のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/c97a5061-b11b-0f50-eb22-294fbb26bee9.png)

次に、以下のコードのようにst.markdown()を利用してCSSスタイルを適用します。
```python:app.py
import streamlit as st

# HTML埋め込み
st.markdown("""
    <style>
    .my-text {
        color: white;
        font-size: 24px;
        background-color: #008080;
        padding: 10px;
        border-radius: 10px;
    }
    </style>
    <p class='my-text'>Hello World!</p>
    """, unsafe_allow_html=True)
```
すると表示されるページは以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/843d3cdc-3551-068d-c944-5bf5d1ec0dc3.png)


# 2. やりたいこと
CSSが適用できるといってもテキストだけだと味気ないので、st.markdown()の中に画像を埋め込もうと思います。
上の画像の"Hello World!"の下に画像を表示させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/b7fa24a3-86e5-cda8-d1f3-4d6a3a02bc75.png)

# 3. [失敗]普通のHTMLの記述方法で試してみる

今回はstreamlitのst.markdown()にstyleタグを埋め込んでスタイルを変更しています。
まずは、イメージの指定`<img src="PATH_TO_IMAGE">`を入れてみます。

```python:app.py
import streamlit as st

# HTML埋め込み
st.markdown("""
    <style>
    .my-text {
        color: white;
        font-size: 24px;
        background-color: #008080;
        padding: 10px;
        border-radius: 10px;
    }
    </style>
    <p class='my-text'>Hello World!<br>
    <img src="image.png" height="150" width="150" style="vertical-align:middle;"></p>
    """, unsafe_allow_html=True)
```

修正後、アプリケーションを実行してみますが、画像は表示されませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/01bfeef8-71fa-1116-cf77-5b86bf6e0088.png)

どうやら普通のHTMLの記述では表示できないようです。

# 4. [成功]streamlitのバージョンが1.18以上の場合
streamlitのバージョン1.18以降に静的コンテンツを表示する機能が追加されているみたいです。
以下の手順で画像を表示することができます。

### 4.1. `static`ディレクトリを作成
streamlitのアプリケーションが動いているディレクトリの中に`static`ディレクトリを作成します。

### 4.2. 画像ファイルの配置
作成した`static`ディレクトリの中に画像ファイル(image.png)を入れます。
`src`ディレクトリの構成は以下のようになります。
```
src
├── app.py
└── static
    └── image.png
```

### 4.3. 実行コマンドに`--server.enableStaticServing=true`をつける
- 変更前
    ```
    $ streamlit run app.py --server.port=8501
    ```
- 変更後
    ```
    $ streamlit run app.py --server.port=8501 --server.enableStaticServing=true
    ```

### 4.4. ファイルの呼び出しパスの変更
4.3で変更したコマンドでstreamlitを実行すると、`static`ディレクトリ内のディレクトリやファイルを`./app/static`から呼び出すことができるようになります。

- 変更前
    ```python
    <img src="image.png" height="150" width="150" style="vertical-align:middle;">
    ```
- 変更後
    ```python
    <img src="./app/static/image.png" height="150" width="150" style="vertical-align:middle;">
    ```

変更後、無事画像を表示させることができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/bb3f1fb0-b633-70ef-95f6-08d3b3b564a4.png)

コード全体はこちらです。
```python:app.py
import streamlit as st

# HTML埋め込み
st.markdown("""
    <style>
    .my-text {
        color: white;
        font-size: 24px;
        background-color: #008080;
        padding: 10px;
        border-radius: 10px;
    }
    </style>
    <p class='my-text'>Hello World!<br>
    <img src=".app/static/image.png" height="150" width="150" style="vertical-align:middle;"></p>
    """, unsafe_allow_html=True)
```

# 5. [成功]streamlitのバージョンが1.18未満の場合
streamlitのバージョンが1.18未満の場合、上記の方法では画像を表示させることができません。
その場合、以下の方法で表示させることができます。

### 5.1. 事前に画像を読み込む
streamlitで画像を表示させる前に画像を読み込みます。
読み込んだ画像はBase64でエンコードし、バイナリデータをテキストデータに変換します。
```python
import base64
from pathlib import Path

image = "image.png"
image_bytes = Path(image).read_bytes()
image_encoded = base64.b64encode(image_bytes).decode()
```

### 5.2. 変換したテキストデータを埋め込む
変換したテキストデータをHTML記述の中に埋め込みます。
- 変更前
    ```python
    <img src="image.png" height="150" width="150" style="vertical-align:middle;">
    ```
- 変更後
    ```python
    # f-stringsで埋め込んでいます
    <img src="data:image/png;base64,{image_encoded}" height="150" width="150" style="vertical-align:middle;">
    ```
変更後、こちらの方法でも画像を表示させることができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/ec0870f7-ed8c-428c-f362-abd747695c20.png)

コード全体はこちらです。
```python:app.py
import streamlit as st
import base64
from pathlib import Path

image = "image.png"
image_bytes = Path(image).read_bytes()
image_encoded = base64.b64encode(image_bytes).decode()

# HTML埋め込み
st.markdown("""
    <style>
    .my-text {
        color: white;
        font-size: 24px;
        background-color: #008080;
        padding: 10px;
        border-radius: 10px;
    }
    </style>
    """
    + f"""
    <p class='my-text'>Hello World!<br>
    <img src="data:image/png;base64,{image_encoded}" height="150" width="150" style="vertical-align:middle;"></p>
    """, unsafe_allow_html=True)
```

# 6. おわりに
streamlitを使った画像の表示は`st.image()`で簡単にできますが、`st.markdown()`の中に画像を入れたい場合は本記事で紹介したような方法で実現することができます。

# 参考文献

- [Serving content from static location](https://github.com/streamlit/streamlit/issues/1567)
- [Static file serving](https://docs.streamlit.io/library/advanced-features/static-file-serving)