1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Asepriteで描いた1枚絵をMSXで表示してみる

Last updated at Posted at 2021-10-31

この記事について

Aseprite という描画ツールを使って作成した、256x192 ピクセルの 1枚絵 を MSX1 (TMS9918A) で表示するまでの手順をサンプルコード付きで記します。

(1) 元画像の準備

私は絵の方は全然なので、iPhone のカメラで撮影した写真を使います。
IMG_1425 2.JPG

まず、この画像をプレビューで 256x192 に縮小します。
iPhoneのカメラのデフォルトの画角が 256x192 と偶然同じだったので、幅256で縦横比固定で縮小すればOKです。

スクリーンショット 2021-10-31 14.36.46.png

これで、元画像の準備完了です。
IMG_1425 2.JPG

(2) Aseprite で減色

Aseprite を起動して 256x192 のスプライトを カラーモード Indexed で新規作成します。
スクリーンショット 2021-10-31 14.53.57.png

そして、パレットを PresetsMSX1 にします。
スクリーンショット 2021-10-31 14.45.47.png

その状態で、準備した元画像をプレビューから ⌘A → ⌘C でコピーして、Aseprise で ⌘P でペーストすれば、(思っていたものとちょっと違うかもしれませんが)減色された画像を作ることができます。
スクリーンショット 2021-10-31 14.57.47.png

とりあえず、この画像をビットマップ形式(image.bmp)で保存しておきます。

(3) TMS9918A 形式への変換

作成した image.bmp を TMS9918A で表示できる形式に変換する良い感じのツールは存在しないので、自前で作る必要があります。

以下に 256x192 8bit カラーの Bitmap (image.bmp) から、TMS9918A の Pattern Generator Table と Color Table を作成するコマンドラインプログラムを示します。

bmp2tms.c
/* 8bit Bitmap to TMS9918A mode-2 Pattern Generator Table & Color Table */
# include <stdio.h>
# include <stdlib.h>
# include <string.h>

/* bmpファイルの情報ヘッダ */
struct DatHead {
    int isize;             /* 情報ヘッダサイズ */
    int width;             /* 幅 */
    int height;            /* 高さ */
    unsigned short planes; /* プレーン数 */
    unsigned short bits;   /* 色ビット数 */
    unsigned int ctype;    /* 圧縮形式 */
    unsigned int gsize;    /* 画像データサイズ */
    int xppm;              /* X方向解像度 */
    int yppm;              /* Y方向解像度 */
    unsigned int cnum;     /* 使用色数 */
    unsigned int inum;     /* 重要色数 */
};

int main(int argc, char* argv[])
{
    FILE* fp = NULL;
    int rc = 0;
    char fh[14];
    int pal[256];
    struct DatHead dh;
    int i, j, k, x, ln;
    unsigned char bmp[256 * 192];
    unsigned char ptn[8 * 768]; // 6KB
    unsigned char col[8 * 768]; // 6KB

    /* 引数チェック */
    rc++;
    if (argc < 4) {
        fprintf(stderr, "usage: bmp2tms input.bmp output.ptn output.col\n");
        goto ENDPROC;
    }

    /* bmpファイルをオープン */
    rc++;
    if (NULL == (fp = fopen(argv[1], "rb"))) {
        fprintf(stderr, "ERROR: Could not open: %s\n", argv[1]);
        goto ENDPROC;
    }

    /* ファイルヘッダを読み込む */
    rc++;
    if (sizeof(fh) != fread(fh, 1, sizeof(fh), fp)) {
        fprintf(stderr, "ERROR: Invalid file header.\n");
        goto ENDPROC;
    }

    /* 先頭2バイトだけ読む */
    rc++;
    if (strncmp(fh, "BM", 2)) {
        fprintf(stderr, "ERROR: Inuput file is not bitmap.\n");
        goto ENDPROC;
    }

    /* 情報ヘッダを読み込む */
    rc++;
    if (sizeof(dh) != fread(&dh, 1, sizeof(dh), fp)) {
        fprintf(stderr, "ERROR: Invalid bitmap file header.\n");
        goto ENDPROC;
    }

    printf("INPUT: width=%d, height=%d, bits=%d(%d), cmp=%d\n", dh.width, dh.height, (int)dh.bits, dh.cnum, dh.ctype);

    /* 256x192でなければエラー扱い */
    rc++;
    if (256 != dh.width || 192 != dh.height) {
        fprintf(stderr, "ERROR: Invalid input bitmap size. (256x192 only)");
        goto ENDPROC;
    }

    /* 8ビットカラー以外は弾く */
    rc++;
    if (8 != dh.bits) {
        fprintf(stderr, "ERROR: Invalid input bitmap color. (8bit color only)\n");
        goto ENDPROC;
    }

    /* 無圧縮以外は弾く */
    rc++;
    if (dh.ctype) {
        fprintf(stderr, "ERROR: This program supports only none-compress type.\n");
        goto ENDPROC;
    }

    /* パレットを読み飛ばす */
    rc++;
    if (sizeof(pal) != fread(pal, 1, sizeof(pal), fp)) {
        fprintf(stderr, "ERROR: Could not read palette data.\n");
        goto ENDPROC;
    }

    /* 画像データを上下反転しながら読み込む */
    rc++;
    for (i = 191; 0 <= i; i--) {
        if (256 != fread(&bmp[i * 256], 1, 256, fp)) {
            fprintf(stderr, "ERROR: Could not read graphic data.\n");
            goto ENDPROC;
        }
    }

    /* 色情報を mod 16 (0~15) にしておく*/
    for (i = 0; i < sizeof(bmp); i++) {
        bmp[i] = bmp[i] & 0x0F;
    }

    /* TMS9918A の Pattern Generator Table と Color Table の形式に変換 */
    k = 0;
    for (i = 0; i < 768; i++) {
        j = i % 32 * 8 + i / 32 * 256 * 8;
        for (ln = 0; ln < 8; ln++, k++, j += 256) {
            /* Color Table */
            unsigned char c[2] = {0, 0};
            for (x = 0; x < 8; x++) {
                if (0 == c[0]) {
                    c[0] = bmp[j + x];
                } else if (c[0] != bmp[j + x]) {
                    if (c[1]) {
                        if (c[1] != bmp[j + x]) {
                            // 横 8px に 3色 以上使われているので無視
                            printf("warning: ignore pixel at (%d, %d)\n", (j + x) % 256, (j + x) / 256);
                        }
                    } else {
                        c[1] = bmp[j + x];
                    }
                }
            }
            col[k] = (c[1] << 4) | c[0];
            /* Pattern Generator Table */
            ptn[k] = 0;
            for (x = 0; x < 8; x++) {
                ptn[k] <<= 1;
                ptn[k] |= bmp[j + x] == c[1] ? 1 : 0;
            }
        }
    }

    /* Pattern Generator Table を書き込み */
    fclose(fp);
    if (NULL == (fp = fopen(argv[2], "wb"))) {
        fprintf(stderr, "ERROR: Could not open: %s\n", argv[2]);
        goto ENDPROC;
    }
    if (sizeof(ptn) != fwrite(ptn, 1, sizeof(ptn), fp)) {
        fprintf(stderr, "ERROR: File write error: %s\n", argv[2]);
        goto ENDPROC;
    }

    /* Color Table を書き込み */
    fclose(fp);
    if (NULL == (fp = fopen(argv[3], "wb"))) {
        fprintf(stderr, "ERROR: Could not open: %s\n", argv[3]);
        goto ENDPROC;
    }
    if (sizeof(ptn) != fwrite(col, 1, sizeof(ptn), fp)) {
        fprintf(stderr, "ERROR: File write error: %s\n", argv[3]);
        goto ENDPROC;
    }

    rc = 0;

    printf("succeed.\n");

    /* 終了処理 */
ENDPROC:
    if (fp) fclose(fp);
    return rc;
}

上記プログラムを用いて、以下のように実行すれば、image.ptn (Pattern Generator Table)と image.col(Color Table)という 各6KB のファイルが生成されます。

clang -o bmp2tms bmp2tms.c
./bmp2tms image.bmp image.ptn image.col

なお、TMS9918A の mode-2 のキャラクタパターン(8x8)は、横 8px につき 2色 しか使えない制約があります。

image.bmp には、この制約に引っかかる箇所が幾つかある状態なので、 bmp2tms は次のような warning を出力して減色する仕様です。この warning を全部対処すれば、image.bmp と完全に同じ画像を MSX で表示できますが、面倒なので今回はこのままいきます。

warning: ignore pixel at (132, 7)
warning: ignore pixel at (133, 7)
warning: ignore pixel at (135, 7)
warning: ignore pixel at (143, 4)
warning: ignore pixel at (142, 6)
(以下、省略)

(4) ROM を作成

今回の ROM は 16KB で次のメモリ配置で作成します。

  • 0x0000 ~ 0x0FFF : プログラム (4KB)
  • 0x1000 ~ 0x27FF : image.ptn (6KB)
  • 0x2800 ~ 0x3FFF : image.col (6KB)

プログラムの実装は次の通りです。

image.asm
org $4000

.Header
    ; MSX の ROM ヘッダ (16 bytes)
    defb 'A', 'B', $10, $40, $00, $00, $00, $00
    defb $00, $00, $00, $00, $00, $00, $00, $00

.Start
    ld sp, $F380
    call VDP_Initialize

    ; Pattern Generator Table を ROM から VRAM へ転送
    ld hl, $0000
    call SetVramAddressFromHL
    ld hl, $4000 + 4096
    ld a, ($0007)
    ld c, a
    ld b, 0
    ld d, 24 ; 256 x 24 = 6144
PatternSetLoop:
    otir
    dec d
    jnz PatternSetLoop

    ; Color Table を ROM から VRAM へ転送
    ld hl, $2000
    call SetVramAddressFromHL
    ld hl, $4000 + 4096 + 6144
    ld a, ($0007)
    ld c, a
    ld b, 0
    ld d, 24
ColorSetLoop:
    otir
    dec d
    jnz ColorSetLoop

    ; Name Table を 上8行 = 0 ~ 255, 中8行 = 0 ~ 255, 下8行 = 0 ~ 255 で埋める
    ld hl, $1800
    call SetVramAddressFromHL
    ld a, ($0007)
    ld c, a
    ld b, 0
    ld d, 3
    ld a, 0
NameSetLoop:
    out (c), a
    inc a
    djnz NameSetLoop
    dec d
    jnz NameSetLoop

.End
    jmp End

; VRAM のアドレスを HL 設定値に書き込みモードで設定
.SetVramAddressFromHL
    di
    ld a, ($0007)
    inc a
    ld c, a
    ld a, l
    out (c), a
    ld a, h
    or $40
    out (c), a
    ei
    ret

; VDP を mode-2 で 768 パターン使う構成に初期化
.VDP_Initialize
    ld a, ($0007)        ; TMS9918A の書き込みアクセスポート番号を取得
    inc a                ; ポート番号を+1
    ld c, a
    ; ld c, $BF          ; 参考: SG-1000で動かす場合
    di

    ; レジスタ #0 (CTRL1) を更新
    ld a, %00000010      ; M2=1, EXTVID=0
    out (c), a
    ld a, %10000000 + 0
    out (c), a

    ; レジスタ #1 (CTRL2) を更新
    ld a, %11100010      ; 4K/16K=1, BL=1, GINT=1, M1=0, M3=0, SI=1, MAG=0
    out (c), a
    ld a, %10000000 + 1
    out (c), a

    ; レジスタ #2 (Pattern Name Tableアドレス) を更新
    ld a, %00000110      ; PN = $1800 (PN13=0, PN12=1, PN11=1, PN10=0)
    out (c), a
    ld a, %10000000 + 2
    out (c), a

    ; レジスタ #3 (Color Tableアドレス+マスク) を更新
    ld a, %11111111      ; CT = $2000, MASK = %11 (768 パターン)
    out (c), a
    ld a, %10000000 + 3
    out (c), a

    ; レジスタ #4 (Pattern Generator Tableアドレス+マスク) を更新
    ld a, %00000011      ; PG = $0000, MASK = %11 (768 パターン)
    out (c), a
    ld a, %10000000 + 4
    out (c), a

    ; レジスタ #5 (Sprite Attributeアドレス) を更新
    ld a, %01101100      ; SA = $1B00
    out (c), a
    ld a, %10000000 + 5
    out (c), a

    ; レジスタ #6 (Sprite Generatorアドレス) を更新
    ld a, %00000111      ; SG = $3800
    out (c), a
    ld a, %10000000 + 6
    out (c), a

    ; レジスタ #7 (TextColor, BackdropColor) を更新
    ld a, %11110001      ; TC = 15 (White), BD = 1 (Black)
    out (c), a
    ld a, %10000000 + 7
    out (c), a

    ei
    ret

アセンブルから ROMファイル (image.rom) 生成までの手順は、次の通りです。

z80asm -b image.asm
dd bs=4k conv=sync if=image.bin of=image_4k.rom
cat image_4k.rom image.ptn image.col > image.rom

(5) WebMSX で表示

作成した image.rom を https://webmsx.org の MSX1 で読み込むと、次のように画像が表示されます。

スクリーンショット 2021-10-31 15.19.09.png

元画像からだいぶ変化しましたが、ひとまず「Asepriteで描いた1枚絵をMSXで表示」という題目通りの目標は達成できました。

減色具合の変化を分かりやすくするため、横に並べてみます。

元画像 Aseprite減色 TMS9918A減色
IMG_1425 2.JPG スクリーンショット 2021-10-31 14.57.47.png スクリーンショット 2021-10-31 15.19.09.png

TMS9918A減色でジャギーになってしまっている部分は、bmp2tms で warning が出ないように修正すればキレイにすることができます。(かなり面倒なので省略)

頑張ればこういう感じの画像を描くこともできます。
スクリーンショット 2021-10-31 15.39.04.png

レーザーの部分はスプライトなので、その部分を除けば 横 8px につき 2色 というルールに則って描かれていることがわかります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?