その①:局ロゴのPNG化
その②:その①の自動化(logo2png)
局ロゴからインデックス値を抽出
ピクセルごとに色が記録される一般的なフルカラーのPNGと違い、局ロゴのようなカラータイプ3(インデックスカラー)のPNGの場合はピクセルごとにインデックス値が記録されている。
局ロゴではインデックス値に対応する色が、0→黒、1→赤、2→緑、3→黄、4→青…と既に決まっているため、カラーパレットがなくてもインデックス値が分かるだけで画像として再現できることになる。
局ロゴはインデックス値が0~127の128色(128値)のみで構成されている。(固定の128色しか使えない)
実際に黒は0、赤は1、白は7のように色をインデックス値に置き換えてみると、局ロゴが数値の集まりに見えてくるはず。
局ロゴのCSV化
局ロゴのファイルに記録されているインデックス値を、そのままCSV化してみる。
コード例
using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
class Program
{
class Chunk
{
public string Type;
public byte[] Data;
}
static void Main(string[] args)
{
foreach (string path in args)
{
using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
{
if (!IsPng(br.ReadBytes(8))) continue;
List<Chunk> chunks = ReadChunks(br);
Chunk ihdrChunk = chunks.Find(c => c.Type == "IHDR");
if (ihdrChunk == null || ihdrChunk.Data[9] != 3) continue; // カラータイプ3のみ対象
int width = ReadInt(ihdrChunk.Data, 0);
int height = ReadInt(ihdrChunk.Data, 4);
Chunk idatChunk = chunks.Find(c => c.Type == "IDAT");
byte[] raw = DecompressIdat(idatChunk.Data);
string csvPath = Path.ChangeExtension(path, ".csv");
WriteCsv(raw, width, height, csvPath);
Console.WriteLine("変換完了: " + Path.GetFileName(csvPath));
}
}
}
static List<Chunk> ReadChunks(BinaryReader br) // チャンク読込
{
var chunks = new List<Chunk>();
while (br.BaseStream.Position < br.BaseStream.Length)
{
int len = ReadInt(br.ReadBytes(4), 0);
string type = new string(br.ReadChars(4));
byte[] data = br.ReadBytes(len);
br.ReadBytes(4); // CRCスキップ
chunks.Add(new Chunk { Type = type, Data = data });
}
return chunks;
}
static byte[] DecompressIdat(byte[] idatRaw) // IDATチャンク解凍
{
using (MemoryStream zstream = new MemoryStream(idatRaw, 2, idatRaw.Length - 2)) // zlibヘッダースキップ
using (DeflateStream deflate = new DeflateStream(zstream, CompressionMode.Decompress))
using (MemoryStream outStream = new MemoryStream())
{
deflate.CopyTo(outStream);
return outStream.ToArray();
}
}
static void WriteCsv(byte[] raw, int width, int height, string csvPath) // CSV生成
{
int stride = width + 1;
byte[] prevLine = new byte[width];
using (StreamWriter sw = new StreamWriter(csvPath))
{
for (int y = 0; y < height; y++)
{
int offset = y * stride;
byte filterType = raw[offset];
byte[] line = new byte[width];
for (int x = 0; x < width; x++)
{
byte rawByte = raw[offset + 1 + x];
byte left = x > 0 ? line[x - 1] : (byte)0;
byte up = prevLine[x];
byte upLeft = x > 0 ? prevLine[x - 1] : (byte)0;
line[x] = ApplyFilter(filterType, rawByte, left, up, upLeft);
}
prevLine = line;
sw.WriteLine(string.Join(",", Array.ConvertAll(line, b => b.ToString())));
}
}
}
static bool IsPng(byte[] sig) // PNGシグネチャ確認
{
byte[] expected = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
for (int i = 0; i < 8; i++)
if (sig[i] != expected[i]) return false;
return true;
}
static int ReadInt(byte[] bytes, int offset) // バイト順変換
{
byte[] temp = new byte[4];
Array.Copy(bytes, offset, temp, 0, 4);
Array.Reverse(temp);
return BitConverter.ToInt32(temp, 0);
}
static byte ApplyFilter(byte filterType, byte rawByte, byte left, byte up, byte upLeft) // フィルタタイプ判別
{
switch (filterType)
{
case 0: return rawByte;
case 1: return (byte)(rawByte + left);
case 2: return (byte)(rawByte + up);
case 3: return (byte)(rawByte + ((left + up) / 2));
case 4: return (byte)(rawByte + PaethPredictor(left, up, upLeft));
default: throw new Exception("不明なフィルタ: " + filterType);
}
}
static byte PaethPredictor(byte a, byte b, byte c) // フィルタタイプ4
{
int p = a + b - c;
int pa = Math.Abs(p - a);
int pb = Math.Abs(p - b);
int pc = Math.Abs(p - c);
if (pa <= pb && pa <= pc) return a;
else if (pb <= pc) return b;
else return c;
}
}
インデックス値の抽出だけなので、変換元ファイルは生データファイルと汎用PNG化したファイルのどちらでもOK。
出力例
CSVになっても陰影が見える…
NCCの透過部分のインデックス値も、透明色の「8」で抽出。
CSVに着色
CSVを表計算ソフトで開き、インデックス値に対応する色を塗ってみる。
条件付き書式で128色分のインデックス値に対応する色を設定すれば、表計算ソフトが受信機のように共通固定色を持ったような状態となる。
つづく…
制作協力:Copilot




