lz4圧縮は圧縮展開速度が速く、圧縮率はそれなりですが、キャッシュとして使いやすい圧縮方式だと思います。今回lz4圧縮で画像ファイルを作成してみたいと思います。
.liz ファイル形式仕様(リズ形式)
.liz
は、LZ4圧縮された画像データを格納する独自形式のファイルフォーマットです。ヘッダーは32バイトの固定長で構成され、画像の基本情報と圧縮サイズを含みます。
ファイル構造
+------------+--------+--------------------------------------------+ | オフセット | サイズ | 内容 | +------------+--------+--------------------------------------------+ | 0 | 3バイト | マジックバイト 'l' 'i' 'z' | | 3 | 1バイト | バージョン(例: 1) | | 4 | 1バイト | ビット深度(例: 8/24/32) | | 5 | 1バイト | チャンネル数(例: 1=G, 3=RGB, 4=RGBA) | | 6 | 2バイト | 予約領域(将来拡張用、0埋め) | | 8 | 4バイト | 幅(Width)int32(リトルエンディアン) | | 12 | 4バイト | 高さ(Height)int32 | | 16 | 4バイト | 展開後サイズ(UncompressedSize)バイト単位 | | 20 | 4バイト | 圧縮後サイズ(CompressedSize)バイト単位 | | 24 | 8バイト | 拡張用予約領域(0埋め) | +------------+--------+--------------------------------------------+
圧縮データ
ヘッダーの直後(32バイト目以降)に、LZ4で圧縮されたピクセルデータが格納されます。展開時には UncompressedSize
をもとにバッファを確保し、LZ4デコードで復元します。
WPFとの統合(WriteableBitmap)
展開されたピクセルデータ(byte[]
)は、WPFの WriteableBitmap
に直接書き込んで表示できます。
これは BitmapImage
のような圧縮画像ではなく、生のピクセルマップ(BGRA/RGBA形式)を想定しています。
使用例(C#)
// 展開後の byte[] を WriteableBitmap に書き込む例
var bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
bitmap.WritePixels(
new Int32Rect(0, 0, width, height),
pixelData, // LZ4で展開したバッファ
width * 4, // stride(4バイト/pixel)
0 // バッファの先頭から
);
特徴
- ヘッダーは32バイト固定長で、高速なパースが可能
- LZ4圧縮による高速な展開性能
- 画像のキャッシュや一時保存用途に適する軽量形式
- 今後の拡張に対応可能な予約領域を確保
備考
- ビット深度:8=グレースケール、24=RGB、32=RGBA を想定
- チャンネル数はピクセルフォーマットと一致させる
- 圧縮形式は LZ4 固定。マジックバイト
'l''i''z'
で識別可能 - 構造が単純なため、C/C++ や他言語でも容易に実装可能
実装プログラム
使用ライブラリ
- K4os.Compression.LZ4
- WPF標準 (System.Windows.Media.Imaging)
プロジェクトの作成
cd (mkdir LizFormat01 -Force)
dotnet new console -f net8.0
dotnet add package K4os.Compression.LZ4
ソースコード
cd (mkdir LizFormat01 -Force)
dotnet new console -f net8.0
dotnet add package K4os.Compression.LZ4
ファイル名:LizFormat01.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" />
</ItemGroup>
</Project>
ファイル名:LizFormat.cs
using K4os.Compression.LZ4;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace CommonLib;
public static class LizFormat
{
private const int HeaderSize = 32;
public static void EncodeToLiz(WriteableBitmap bitmap, Stream output)
{
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int channels = 4;
int bitDepth = 32;
int stride = width * channels;
int rawSize = stride * height;
byte[] rawPixels = new byte[rawSize];
bitmap.CopyPixels(rawPixels, stride, 0);
byte[] compressed = new byte[LZ4Codec.MaximumOutputSize(rawSize)];
int compressedLength = LZ4Codec.Encode(rawPixels, 0, rawSize, compressed, 0, compressed.Length);
Array.Resize(ref compressed, compressedLength);
using BinaryWriter writer = new(output, System.Text.Encoding.UTF8, leaveOpen: true);
writer.Write(new byte[] { (byte)'l', (byte)'i', (byte)'z' }); // magic
writer.Write((byte)1); // version
writer.Write((byte)bitDepth); // bit depth
writer.Write((byte)channels); // channels
writer.Write((ushort)0); // reserved
writer.Write(width);
writer.Write(height);
writer.Write(rawSize); // uncompressed size
writer.Write(compressedLength); // compressed size
writer.Write(new byte[8]); // reserved
writer.Write(compressed); // data
}
public static WriteableBitmap DecodeFromLiz(Stream input)
{
using BinaryReader reader = new(input, System.Text.Encoding.UTF8, leaveOpen: true);
byte[] magic = reader.ReadBytes(3);
if (magic[0] != 'l' || magic[1] != 'i' || magic[2] != 'z')
throw new InvalidDataException("Invalid .liz format");
byte version = reader.ReadByte();
byte bitDepth = reader.ReadByte();
byte channels = reader.ReadByte();
reader.ReadUInt16(); // reserved
int width = reader.ReadInt32();
int height = reader.ReadInt32();
int uncompressedSize = reader.ReadInt32();
int compressedSize = reader.ReadInt32();
reader.ReadBytes(8); // reserved
byte[] compressed = reader.ReadBytes(compressedSize);
byte[] rawPixels = new byte[uncompressedSize];
LZ4Codec.Decode(compressed, 0, compressedSize, rawPixels, 0, uncompressedSize);
var bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), rawPixels, width * 4, 0);
return bitmap;
}
}
ファイル名:Program.cs
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Imaging;
using CommonLib;
class Program
{
static void ConvertPngToLiz(string inputPngFile, string outputLizFile)
{
byte[] pngBytes = File.ReadAllBytes(inputPngFile);
var sw = Stopwatch.StartNew();
// --- PNG -> WriteableBitmap ---
BitmapImage png = new();
png.BeginInit();
png.CacheOption = BitmapCacheOption.OnLoad;
png.StreamSource = new MemoryStream(pngBytes);
png.EndInit();
sw.Stop();
Console.WriteLine($"PNG Decode:{sw.ElapsedMilliseconds}ms");
WriteableBitmap wbmp = new(png);
sw.Restart();
using var lizStream = new MemoryStream();
// --- WriteableBitmap -> .liz (Streamで書き込み) ---
LizFormat.EncodeToLiz(wbmp, lizStream);
sw.Stop();
Console.WriteLine($"Liz Encode:{sw.ElapsedMilliseconds}ms");
File.WriteAllBytes(outputLizFile, lizStream.ToArray());
}
static void ConvertLizToPng(string inputLizFile, string outputPngFile)
{
byte[] lizBytes = File.ReadAllBytes(inputLizFile);
var sw = Stopwatch.StartNew();
// --- .liz -> WriteableBitmap (Streamで読み込み) ---
WriteableBitmap restoredBitmap;
var lizInput = new MemoryStream(lizBytes);
restoredBitmap = LizFormat.DecodeFromLiz(lizInput);
sw.Stop();
Console.WriteLine($"Liz Decode:{sw.ElapsedMilliseconds}ms");
sw.Restart();
// --- WriteableBitmap -> PNG 保存 ---
using var pngOut = new MemoryStream();
PngBitmapEncoder encoder = new();
encoder.Frames.Add(BitmapFrame.Create(restoredBitmap));
encoder.Save(pngOut);
sw.Stop();
Console.WriteLine($"PNG Encode:{sw.ElapsedMilliseconds}ms");
File.WriteAllBytes(outputPngFile, pngOut.ToArray());
}
static void Main()
{
string inputPngPath = @"H:\csharp\console\LizFormat01\sample2.png";
string outputLizPath = @"H:\csharp\console\LizFormat01\sample2.liz";
string restoredPngPath = @"H:\csharp\console\LizFormat01\restored2.png";
ConvertPngToLiz(inputPngPath, outputLizPath);
ConvertLizToPng(outputLizPath, restoredPngPath);
Console.WriteLine("変換完了: PNG → LIZ → PNG");
var fi = new FileInfo(inputPngPath);
Console.WriteLine($"変換前PNG:{fi.Length / 1024}KB");
var fi2 = new FileInfo(outputLizPath);
Console.WriteLine($"変換後liz:{fi2.Length / 1024}KB");
}
}
/*
結果
Celeron 3965U
素材:写真
PNG Decode:213ms
Liz Encode:29ms
Liz Decode:10ms
PNG Encode:32ms
変換完了: PNG → LIZ → PNG
変換前PNG:281KB
変換後liz:410KB
素材:アプリのスクショ
PNG Decode:274ms
Liz Encode:11ms
Liz Decode:11ms
PNG Encode:19ms
変換完了: PNG → LIZ → PNG
変換前PNG:7KB
変換後liz:10KB
*/
PNGよりファイルサイズが大きいですが、その分エンコード・デコードの時間が短い傾向になりました。
コメント