PNG第3版に対応する便利クラス
// CPngKit.h
#pragma once
#ifdef CPNGKIT_EXPORTS
#define CPNGKIT_API __declspec(dllexport)
#else
#define CPNGKIT_API __declspec(dllimport)
#endif
#include <vector>
#include <string>
#include <cstdint>
// C#側で扱うためのPNGファイルハンドル(実体はC++のPngFileクラスへのポインタ)
typedef void* HPNG;
extern "C" {
// --- エラーコード定義 ---
enum CPNGKIT_RESULT {
SUCCESS = 0,
ERROR_INVALID_SIGNATURE = -1,
ERROR_CRC_MISMATCH = -2,
ERROR_INVALID_ARGUMENT = -3,
ERROR_FILE_IO = -4,
ERROR_NULL_POINTER = -5,
ERROR_UNKNOWN = -100
};
// --- データ構造体 ---
// C#とやり取りするためのIHDRチャンク情報
#pragma pack(push, 1) // メモリのアライメントを1バイトに設定し、構造体のサイズを固定
struct IhdrData {
uint32_t Width;
uint32_t Height;
uint8_t BitDepth;
uint8_t ColorType;
uint8_t CompressionMethod;
uint8_t FilterMethod;
uint8_t InterlaceMethod;
};
// APNGのアニメーション制御情報 (acTL)
struct AcTlData {
uint32_t NumFrames;
uint32_t NumPlays;
};
// APNGのフレーム制御情報 (fcTL)
struct FcTlData {
uint32_t SequenceNumber;
uint32_t Width;
uint32_t Height;
uint32_t XOffset;
uint32_t YOffset;
uint16_t DelayNum;
uint16_t DelayDen;
uint8_t DisposeOp;
uint8_t BlendOp;
};
// HDRのカラープロファイル情報 (cICP)
struct CicpData {
uint8_t ColorPrimaries;
uint8_t TransferFunction;
uint8_t MatrixCoefficients;
uint8_t VideoFullRangeFlag;
};
#pragma pack(pop)
// --- PNGファイルの読み込み・解放 ---
/// <summary>
/// 指定されたファイルパスからPNGファイルを読み込み、ハンドルを返す。
/// </summary>
/// <param name="filePath">読み込むPNGファイルのパス(UTF-8文字列)。</param>
/// <param name="pngHandle">成功した場合、生成されたPNGファイルハンドルが格納されるポインタ。</param>
/// <returns>処理結果を示すエラーコード(SUCCESS_CODEなら成功)。</returns>
CPNGKIT_API int PngFile_ReadFromFile(const char* filePath, HPNG* pngHandle);
/// <summary>
/// PngFile_ReadFromFileで確保されたPNGファイルハンドルを解放する。
/// </summary>
/// <param name="pngHandle">解放するPNGファイルハンドル。</param>
CPNGKIT_API void PngFile_Release(HPNG pngHandle);
// --- PNG情報の取得 ---
/// <summary>
/// PNGファイルのIHDRチャンク情報を取得する。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <param name="ihdr">IHDRデータを格納する構造体へのポインタ。</param>
/// <returns>処理結果。IHDRが見つからない場合はエラー。</returns>
CPNGKIT_API int PngFile_GetIhdr(HPNG pngHandle, IhdrData* ihdr);
/// <summary>
/// PNGがアニメーションPNG(APNG)かどうかを判定する。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <returns>APNGの場合は1、そうでない場合は0を返す。</returns>
CPNGKIT_API int PngFile_IsAnimated(HPNG pngHandle);
/// <summary>
/// アニメーション情報を取得する(acTLチャンク)。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <param name="actl">acTLデータを格納する構造体へのポインタ。</param>
/// <returns>処理結果。acTLが見つからない場合はエラー。</returns>
CPNGKIT_API int PngFile_GetAnimationInfo(HPNG pngHandle, AcTlData* actl);
/// <summary>
/// 指定したインデックスのフレーム制御情報(fcTL)を取得する。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <param name="frameIndex">取得するフレームのインデックス(0から)。</param>
/// <param name="fctl">fcTLデータを格納する構造体へのポインタ。</param>
/// <returns>処理結果。指定インデックスのフレームが見つからない場合はエラー。</returns>
CPNGKIT_API int PngFile_GetFrameInfo(HPNG pngHandle, int frameIndex, FcTlData* fctl);
/// <summary>
/// eXIfチャンクのデータを取得する。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <param name="buffer">eXIfデータを格納するバッファへのポインタ。事前にサイズを確保しておく必要がある。</param>
/// <param name="bufferSize">バッファのサイズ。</param>
/// <param name="dataSize">実際に書き込まれたデータのサイズが格納される。</param>
/// <returns>処理結果。eXIfチャンクがない場合はエラー。</returns>
CPNGKIT_API int PngFile_GetExifData(HPNG pngHandle, unsigned char* buffer, int bufferSize, int* dataSize);
/// <summary>
/// cICPチャンクのデータを取得する。
/// </summary>
/// <param name="pngHandle">対象のPNGファイルハンドル。</param>
/// <param name="cicp">cICPデータを格納する構造体へのポインタ。</param>
/// <returns>処理結果。cICPチャンクがない場合はエラー。</returns>
CPNGKIT_API int PngFile_GetCICPData(HPNG pngHandle, CicpData* cicp);
// --- PNGファイルの書き込み ---
/// <summary>
/// PNGファイルハンドルを新しいPNGファイルとして保存する。
/// </summary>
/// <param name="pngHandle">保存するPNGファイルハンドル。</param>
/// <param name="filePath">保存先のファイルパス(UTF-8文字列)。</param>
/// <returns>処理結果。</returns>
CPNGKIT_API int PngFile_WriteToFile(HPNG pngHandle, const char* filePath);
}// CPngKit.cpp
#include "pch.h" // プリコンパイル済みヘッダー (Visual Studioのプロジェクト作成時に自動生成される)
#include "CPngKit.h"
#include <fstream>
#include <iostream>
#include <algorithm>
#include <numeric>
// --- 内部実装 ---
// C#のPngHelperに相当する機能
namespace PngUtil {
// ネットワークバイトオーダー(ビッグエンディアン)とホストバイトオーダー(通常リトルエンディアン)の変換
uint32_t SwapEndian(uint32_t val) {
return (val << 24) | ((val << 8) & 0x00ff0000) | ((val >> 8) & 0x0000ff00) | (val >> 24);
}
uint16_t SwapEndian(uint16_t val) {
return (val << 8) | (val >> 8);
}
// ストリームからの読み書き
uint32_t ReadUInt32BigEndian(std::istream& stream) {
uint32_t val;
stream.read(reinterpret_cast<char*>(&val), 4);
return SwapEndian(val);
}
uint16_t ReadUInt16BigEndian(std::istream& stream) {
uint16_t val;
stream.read(reinterpret_cast<char*>(&val), 2);
return SwapEndian(val);
}
void WriteUInt32BigEndian(std::ostream& stream, uint32_t val) {
uint32_t swapped = SwapEndian(val);
stream.write(reinterpret_cast<const char*>(&swapped), 4);
}
void WriteUInt16BigEndian(std::ostream& stream, uint16_t val) {
uint16_t swapped = SwapEndian(val);
stream.write(reinterpret_cast<const char*>(&swapped), 2);
}
}
// C#のCrc32に相当する機能
class Crc32 {
private:
static std::vector<uint32_t> table;
uint32_t crc_val = 0xFFFFFFFF;
static void generate_table() {
if (!table.empty()) return;
table.resize(256);
constexpr uint32_t poly = 0xEDB88320;
for (uint32_t i = 0; i < 256; ++i) {
uint32_t c = i;
for (int k = 0; k < 8; ++k) {
c = (c & 1) ? (poly ^ (c >> 1)) : (c >> 1);
}
table[i] = c;
}
}
public:
Crc32() {
generate_table();
}
void update(const std::vector<uint8_t>& data) {
for (uint8_t byte : data) {
crc_val = table[(crc_val ^ byte) & 0xFF] ^ (crc_val >> 8);
}
}
uint32_t get_value() {
return crc_val ^ 0xFFFFFFFF;
}
static uint32_t calculate(const std::string& type, const std::vector<uint8_t>& data) {
Crc32 crc;
std::vector<uint8_t> type_bytes(type.begin(), type.end());
crc.update(type_bytes);
if (!data.empty()) {
crc.update(data);
}
return crc.get_value();
}
};
std::vector<uint32_t> Crc32::table;
// C#のChunkクラスと派生クラス群をC++で再実装
class Chunk {
public:
std::string Type;
std::vector<uint8_t> Data;
Chunk(const std::string& type) : Type(type) {}
virtual ~Chunk() = default;
void WriteTo(std::ostream& stream) {
BuildData(); // 派生クラスのプロパティからDataを構築
PngUtil::WriteUInt32BigEndian(stream, static_cast<uint32_t>(Data.size()));
stream.write(Type.c_str(), 4);
stream.write(reinterpret_cast<const char*>(Data.data()), Data.size());
uint32_t crc = Crc32::calculate(Type, Data);
PngUtil::WriteUInt32BigEndian(stream, crc);
}
protected:
// 派生クラスで実装。プロパティからDataベクターを更新する。
virtual void BuildData() {}
};
class IhdrChunk : public Chunk {
public:
IhdrData Info;
IhdrChunk(const std::vector<uint8_t>& data) : Chunk("IHDR") {
Data = data;
memcpy(&Info, data.data(), sizeof(IhdrData));
// エンディアン変換が必要
Info.Width = PngUtil::SwapEndian(Info.Width);
Info.Height = PngUtil::SwapEndian(Info.Height);
}
protected:
void BuildData() override {
// InfoからDataを再構築
Data.resize(sizeof(IhdrData));
IhdrData swappedInfo = Info;
swappedInfo.Width = PngUtil::SwapEndian(Info.Width);
swappedInfo.Height = PngUtil::SwapEndian(Info.Height);
memcpy(Data.data(), &swappedInfo, sizeof(IhdrData));
}
};
class FcTlChunk : public Chunk {
public:
FcTlData Info;
FcTlChunk(const std::vector<uint8_t>& data) : Chunk("fcTL") {
Data = data;
std::stringstream ss(std::string(data.begin(), data.end()));
Info.SequenceNumber = PngUtil::ReadUInt32BigEndian(ss);
Info.Width = PngUtil::ReadUInt32BigEndian(ss);
Info.Height = PngUtil::ReadUInt32BigEndian(ss);
Info.XOffset = PngUtil::ReadUInt32BigEndian(ss);
Info.YOffset = PngUtil::ReadUInt32BigEndian(ss);
Info.DelayNum = PngUtil::ReadUInt16BigEndian(ss);
Info.DelayDen = PngUtil::ReadUInt16BigEndian(ss);
ss.read(reinterpret_cast<char*>(&Info.DisposeOp), 1);
ss.read(reinterpret_cast<char*>(&Info.BlendOp), 1);
}
};
// 他のチャンククラスも同様に定義...
class PngFile {
public:
std::vector<std::shared_ptr<Chunk>> Chunks;
};
// --- エクスポート関数の実装 ---
CPNGKIT_API int PngFile_ReadFromFile(const char* filePath, HPNG* pngHandle) {
if (!filePath || !pngHandle) return ERROR_INVALID_ARGUMENT;
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open()) return ERROR_FILE_IO;
// PNGシグネチャの確認
char signature[8];
file.read(signature, 8);
const char png_sig[] = { (char)0x89, 'P', 'N', 'G', '\r', '\n', (char)0x1A, '\n' };
if (memcmp(signature, png_sig, 8) != 0) {
return ERROR_INVALID_SIGNATURE;
}
auto pngFile = new PngFile();
try {
while (file.peek() != EOF) {
uint32_t length = PngUtil::ReadUInt32BigEndian(file);
std::string type(4, '\0');
file.read(&type[0], 4);
std::vector<uint8_t> data(length);
if (length > 0) {
file.read(reinterpret_cast<char*>(data.data()), length);
}
uint32_t crc_read = PngUtil::ReadUInt32BigEndian(file);
if (Crc32::calculate(type, data) != crc_read) {
delete pngFile;
return ERROR_CRC_MISMATCH;
}
if (type == "IHDR") {
pngFile->Chunks.push_back(std::make_shared<IhdrChunk>(data));
} else if (type == "fcTL") {
pngFile->Chunks.push_back(std::make_shared<FcTlChunk>(data));
}
// 他のチャンクも同様にファクトリパターンで生成
else {
auto chunk = std::make_shared<Chunk>(type);
chunk->Data = data;
pngFile->Chunks.push_back(chunk);
}
if (type == "IEND") break;
}
} catch (...) {
delete pngFile;
return ERROR_UNKNOWN;
}
*pngHandle = pngFile;
return SUCCESS;
}
CPNGKIT_API void PngFile_Release(HPNG pngHandle) {
if (pngHandle) {
delete static_cast<PngFile*>(pngHandle);
}
}
CPNGKIT_API int PngFile_GetIhdr(HPNG pngHandle, IhdrData* ihdr) {
if (!pngHandle || !ihdr) return ERROR_NULL_POINTER;
auto pngFile = static_cast<PngFile*>(pngHandle);
for (const auto& chunk : pngFile->Chunks) {
if (chunk->Type == "IHDR") {
auto ihdrChunk = std::dynamic_pointer_cast<IhdrChunk>(chunk);
if (ihdrChunk) {
*ihdr = ihdrChunk->Info;
return SUCCESS;
}
}
}
return ERROR_UNKNOWN; // IHDRが見つからない
}
CPNGKIT_API int PngFile_WriteToFile(HPNG pngHandle, const char* filePath) {
if (!pngHandle || !filePath) return ERROR_NULL_POINTER;
auto pngFile = static_cast<PngFile*>(pngHandle);
std::ofstream file(filePath, std::ios::binary);
if (!file.is_open()) return ERROR_FILE_IO;
// PNGシグネチャ書き込み
const char png_sig[] = { (char)0x89, 'P', 'N', 'G', '\r', '\n', (char)0x1A, '\n' };
file.write(png_sig, 8);
// 各チャンクを書き込み
for (const auto& chunk : pngFile->Chunks) {
chunk->WriteTo(file);
}
return SUCCESS;
}
// 他のエクスポート関数の実装も同様に続ける...
// PngFile_IsAnimated, GetAnimationInfo, GetFrameInfo, GetExifData, GetCICPData などclass Program
{
static void Main(string[] args)
{
string inputPath = "path/to/your/image.png";
string outputPath = "path/to/your/output.png";
try
{
// usingステートメントで自動的にリソース解放
using (var png = CPngKit.FromFile(inputPath))
{
var ihdr = png.GetIhdr();
Console.WriteLine($"Image dimensions: {ihdr.Width} x {ihdr.Height}");
// ここで他の情報(APNG, HDR, Exif)を取得するメソッドを呼び出す
// 読み込んだデータをそのまま別名で保存
png.Save(outputPath);
Console.WriteLine($"File saved to {outputPath}");
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}using SharpDX.Direct3D;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace ImageBuilder
{
// ---------------------------------------------------------------------------
// PNGファイルの仕様が20年以上ぶりにバージョンアップ、HDR・アニメーションPNG・Exifをサポート
// https://gigazine.net/news/20250626-png-update/?utm_source=x&utm_medium=sns&utm_campaign=x_post&utm_content=20250626-png-update
// ポータブル ネットワーク グラフィックス (PNG) 仕様 (第 3 版)
// https://www.w3.org/TR/png-3/
// ---------------------------------------------------------------------------
/// <summary>
/// PNGファイルの読み書きに関する補助機能を提供する静的クラス。
/// PNG仕様で定められているビッグエンディアン形式の数値の変換などを行う。
/// </summary>
public static class PngHelper
{
// ---------------------------------------------------------------------------
/// <summary>
/// PNGファイルの先頭8バイトのシグネチャ。
/// これによりPNGファイルであることを識別する。
/// </summary>
public static readonly byte[] PngSignature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
// ---------------------------------------------------------------------------
/// <summary>
/// BinaryReaderから4バイトを読み込み、ビッグエンディアン形式として解釈して符号なし32ビット整数(uint)を返す。
/// </summary>
/// <param name="reader">読み込み元のBinaryReader。</param>
/// <returns>変換されたuint値。</returns>
public static uint ReadUInt32BigEndian(BinaryReader reader)
{
// 4バイト読み込む
var data = reader.ReadBytes(4);
// 現在のシステムがリトルエンディアンの場合、バイトオーダーを逆転させる
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data);
}
return BitConverter.ToUInt32(data, 0);
}
// ---------------------------------------------------------------------------
/// <summary>
/// BinaryReaderから2バイトを読み込み、ビッグエンディアン形式として解釈して符号なし16ビット整数(ushort)を返す。
/// </summary>
/// <param name="reader">読み込み元のBinaryReader。</param>
/// <returns>変換されたushort値。</returns>
public static ushort ReadUInt16BigEndian(BinaryReader reader)
{
// 2バイト読み込む
var data = reader.ReadBytes(2);
// 現在のシステムがリトルエンディアンの場合、バイトオーダーを逆転させる
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data);
}
return BitConverter.ToUInt16(data, 0);
}
// ---------------------------------------------------------------------------
/// <summary>
/// 符号なし32ビット整数(uint)をビッグエンディアン形式の4バイトとしてBinaryWriterに書き込む。
/// </summary>
/// <param name="writer">書き込み先のBinaryWriter。</param>
/// <param name="value">書き込むuint値。</param>
public static void WriteUInt32BigEndian(BinaryWriter writer, uint value)
{
// uintをバイト配列に変換
var data = BitConverter.GetBytes(value);
// 現在のシステムがリトルエンディアンの場合、バイトオーダーを逆転させる
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data);
}
writer.Write(data);
}
// ---------------------------------------------------------------------------
/// <summary>
/// 符号なし16ビット整数(ushort)をビッグエンディアン形式の2バイトとしてBinaryWriterに書き込む。
/// </summary>
/// <param name="writer">書き込み先のBinaryWriter。</param>
/// <param name="value">書き込むushort値。</param>
public static void WriteUInt16BigEndian(BinaryWriter writer, ushort value)
{
// ushortをバイト配列に変換
var data = BitConverter.GetBytes(value);
// 現在のシステムがリトルエンディアンの場合、バイトオーダーを逆転させる
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data);
}
writer.Write(data);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
/// チャンクのクラス。
/// </summary>
public abstract class Chunk
{
// ---------------------------------------------------------------------------
// プロパティ
// <summary>
/// タイプ。
/// </summary>
public string Type { get; }
/// <summary>
/// データ。
/// </summary>
public byte[] Data { get; protected set; }
// ---------------------------------------------------------------------------
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="type"></param>
protected Chunk(string type_)
{
Type = type_;
Data = Array.Empty<byte>();
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="data"></param>
protected Chunk(string type_, byte[] data_)
{
Type = type_;
Data = data_;
ParseData(new BinaryReader(new MemoryStream(data_)));
}
// ---------------------------------------------------------------------------
/// <summary>
/// 各派生クラスで、Dataをプロパティにパースする処理を実装。
/// </summary>
/// <param name="reader_"></param>
protected abstract void ParseData(BinaryReader reader_);
// ---------------------------------------------------------------------------
/// <summary>
/// 各派生クラスで、プロパティからDataを再構築する処理を実装。
/// </summary>
/// <param name="writer_"></param>
protected abstract void WriteData(BinaryWriter writer_);
// ---------------------------------------------------------------------------
/// <summary>
/// 書き込む。
/// </summary>
/// <param name="writer_"></param>
public void WriteTo(BinaryWriter writer_)
{
// --------------
// データを再構築する。
using (var ms = new MemoryStream())
using (var dataWriter = new BinaryWriter(ms))
{
WriteData(dataWriter);
Data = ms.ToArray();
}
var type_bytes = Encoding.ASCII.GetBytes(Type);
PngHelper.WriteUInt32BigEndian(writer_, (uint)Data.Length);
writer_.Write(type_bytes);
writer_.Write(Data);
uint crc = Crc32.Calculate(type_bytes, Data);
PngHelper.WriteUInt32BigEndian(writer_, crc);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
/// 基本チャンクのサンプルクラス。
/// </summary>
public class IhdrChunk : Chunk
{
// ---------------------------------------------------------------------------
// プロパティ
/// <summary>
/// 横幅。
/// </summary>
public uint Width { get; set; }
/// <summary>
/// 縦幅。
/// </summary>
public uint Height { get; set; }
/// <summary>
/// ビット深度。
/// </summary>
public byte BitDepth { get; set; }
/// <summary>
/// カラータイプ。
/// </summary>
public byte ColorType { get; set; }
/// <summary>
/// 圧縮方式。
/// </summary>
public byte CompressionMethod { get; set; }
/// <summary>
/// フィルター方式。
/// </summary>
public byte FilterMethod { get; set; }
/// <summary>
/// インターレース方式。
/// </summary>
public byte InterlaceMethod { get; set; }
// ---------------------------------------------------------------------------
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="data"></param>
public IhdrChunk(byte[] data_) : base("IHDR", data_)
{
}
// ---------------------------------------------------------------------------
/// <summary>
/// データを読み取る。
/// </summary>
/// <param name="reader_"></param>
protected override void ParseData(BinaryReader reader_)
{
Width = PngHelper.ReadUInt32BigEndian(reader_);
Height = PngHelper.ReadUInt32BigEndian(reader_);
BitDepth = reader_.ReadByte();
ColorType = reader_.ReadByte();
CompressionMethod = reader_.ReadByte();
FilterMethod = reader_.ReadByte();
InterlaceMethod = reader_.ReadByte();
}
// ---------------------------------------------------------------------------
/// <summary>
/// データを書き込む。
/// </summary>
/// <param name="writer_"></param>
protected override void WriteData(BinaryWriter writer_)
{
PngHelper.WriteUInt32BigEndian(writer_, Width);
PngHelper.WriteUInt32BigEndian(writer_, Height);
writer_.Write(BitDepth);
writer_.Write(ColorType);
writer_.Write(CompressionMethod);
writer_.Write(FilterMethod);
writer_.Write(InterlaceMethod);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class IdatChunk : Chunk
{
// ---------------------------------------------------------------------------
// プロパティ
/// <summary>
///
/// </summary>
/// <param name="data"></param>
public IdatChunk(byte[] data) : base("IDAT", data) { }
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
protected override void ParseData(BinaryReader reader) { /* データはそのまま保持 */ }
/// <summary>
///
/// </summary>
/// <param name="writer"></param>
protected override void WriteData(BinaryWriter writer) { writer.Write(Data); }
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class IendChunk : Chunk
{
// ---------------------------------------------------------------------------
//
/// <summary>
///
/// </summary>
public IendChunk() : base("IEND") { }
/// <summary>
///
/// </summary>
/// <param name="data"></param>
public IendChunk(byte[] data) : base("IEND", data) { }
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
protected override void ParseData(BinaryReader reader) { }
/// <summary>
///
/// </summary>
/// <param name="writer"></param>
protected override void WriteData(BinaryWriter writer) { }
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
/// APNGチャンク
/// </summary>
public class AcTlChunk : Chunk
{
// ---------------------------------------------------------------------------
// プロパティ
/// <summary>
///
/// </summary>
public uint NumFrames { get; set; }
/// <summary>
///
/// </summary>
public uint NumPlays { get; set; }
/// <summary>
///
/// </summary>
/// <param name="data"></param>
public AcTlChunk(byte[] data) : base("acTL", data) { }
// ---------------------------------------------------------------------------
/// <summary>
/// データを読み取る。
/// </summary>
/// <param name="reader_"></param>
protected override void ParseData(BinaryReader reader_)
{
NumFrames = PngHelper.ReadUInt32BigEndian(reader_);
NumPlays = PngHelper.ReadUInt32BigEndian(reader_);
}
// ---------------------------------------------------------------------------
/// <summary>
/// データを書き込む。
/// </summary>
/// <param name="writer_"></param>
protected override void WriteData(BinaryWriter writer_)
{
PngHelper.WriteUInt32BigEndian(writer_, NumFrames);
PngHelper.WriteUInt32BigEndian(writer_, NumPlays);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class FcTlChunk : Chunk
{
// ---------------------------------------------------------------------------
// プロパティ
/// <summary>
///
/// </summary>
public uint SequenceNumber { get; set; }
/// <summary>
///
/// </summary>
public uint Width { get; set; }
/// <summary>
///
/// </summary>
public uint Height { get; set; }
/// <summary>
///
/// </summary>
public uint XOffset { get; set; }
/// <summary>
///
/// </summary>
public uint YOffset { get; set; }
/// <summary>
///
/// </summary>
public ushort DelayNum { get; set; }
/// <summary>
///
/// </summary>
public ushort DelayDen { get; set; }
/// <summary>
///
/// </summary>
public byte DisposeOp { get; set; }
/// <summary>
///
/// </summary>
public byte BlendOp { get; set; }
/// <summary>
///
/// </summary>
/// <param name="data"></param>
public FcTlChunk(byte[] data) : base("fcTL", data) { }
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
protected override void ParseData(BinaryReader reader)
{
SequenceNumber = PngHelper.ReadUInt32BigEndian(reader);
Width = PngHelper.ReadUInt32BigEndian(reader);
Height = PngHelper.ReadUInt32BigEndian(reader);
XOffset = PngHelper.ReadUInt32BigEndian(reader);
YOffset = PngHelper.ReadUInt32BigEndian(reader);
DelayNum = PngHelper.ReadUInt16BigEndian(reader);
DelayDen = PngHelper.ReadUInt16BigEndian(reader);
DisposeOp = reader.ReadByte();
BlendOp = reader.ReadByte();
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="writer"></param>
protected override void WriteData(BinaryWriter writer)
{
PngHelper.WriteUInt32BigEndian(writer, SequenceNumber);
PngHelper.WriteUInt32BigEndian(writer, Width);
PngHelper.WriteUInt32BigEndian(writer, Height);
PngHelper.WriteUInt32BigEndian(writer, XOffset);
PngHelper.WriteUInt32BigEndian(writer, YOffset);
PngHelper.WriteUInt16BigEndian(writer, DelayNum);
PngHelper.WriteUInt16BigEndian(writer, DelayDen);
writer.Write(DisposeOp);
writer.Write(BlendOp);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class FdAtChunk : Chunk
{
// ---------------------------------------------------------------------------
//
public uint SequenceNumber { get; set; }
public byte[] FrameData { get; set; }
public FdAtChunk(byte[] data) : base("fdAT", data) { }
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
protected override void ParseData(BinaryReader reader)
{
SequenceNumber = PngHelper.ReadUInt32BigEndian(reader);
FrameData = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position));
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="writer"></param>
protected override void WriteData(BinaryWriter writer)
{
PngHelper.WriteUInt32BigEndian(writer, SequenceNumber);
writer.Write(FrameData);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
// ---- HDRチャンク ----
/// <summary>
///
/// </summary>
public class CICPChunk : Chunk
{
// ---------------------------------------------------------------------------
public byte ColorPrimaries { get; set; }
public byte TransferFunction { get; set; }
public byte MatrixCoefficients { get; set; }
public byte VideoFullRangeFlag { get; set; }
public CICPChunk(byte[] data) : base("cICP", data) { }
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
protected override void ParseData(BinaryReader reader)
{
ColorPrimaries = reader.ReadByte();
TransferFunction = reader.ReadByte();
MatrixCoefficients = reader.ReadByte();
VideoFullRangeFlag = reader.ReadByte();
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="writer"></param>
protected override void WriteData(BinaryWriter writer)
{
writer.Write(ColorPrimaries);
writer.Write(TransferFunction);
writer.Write(MatrixCoefficients);
writer.Write(VideoFullRangeFlag);
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
// 同様にMDVCChunk, CLLIChunkも実装可能
/// <summary>
///
/// </summary>
// ---- Exifチャンク ----
public class ExifChunk : Chunk
{
// ---------------------------------------------------------------------------
// ExifデータはTIFFフォーマットなので、ここではバイト配列として保持する。
// 必要に応じて外部のExifパーサーライブラリを利用する。
public ExifChunk(byte[] data) : base("eXIf", data) { }
protected override void ParseData(BinaryReader reader) { /* Dataをそのまま利用 */ }
protected override void WriteData(BinaryWriter writer) { writer.Write(Data); }
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class AnimationFrame
{
// ---------------------------------------------------------------------------
public FcTlChunk FrameControl { get; set; }
public List<Chunk> FrameDataChunks { get; } = new List<Chunk>(); // IDAT or fdAT
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
public byte[] GetImageData()
{
// IDAT/fdATチャンクのデータを結合し、zlibで解凍する
using (var compressedStream = new MemoryStream())
{
foreach (var chunk in FrameDataChunks)
{
byte[] data = chunk is FdAtChunk fdAt ? fdAt.FrameData : chunk.Data;
compressedStream.Write(data, 0, data.Length);
}
compressedStream.Position = 0;
using (var resultStream = new MemoryStream())
{
// ZlibStreamのヘッダ2バイトをスキップ
compressedStream.Seek(2, SeekOrigin.Begin);
using (var zlibStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
zlibStream.CopyTo(resultStream);
}
return resultStream.ToArray();
}
}
}
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public class PngFile
{
// ---------------------------------------------------------------------------
public List<Chunk> Chunks { get; } = new List<Chunk>();
public bool IsAnimated => Chunks.Any(c => c is AcTlChunk);
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerable<AnimationFrame> GetAnimationFrames()
{
if (!IsAnimated) yield break;
var frame = new AnimationFrame();
bool firstFrame = true;
foreach (var chunk in Chunks)
{
if (chunk is FcTlChunk fcTl)
{
if (frame.FrameControl != null)
{
yield return frame;
}
frame = new AnimationFrame { FrameControl = fcTl };
}
else if (chunk is IdatChunk idat && firstFrame)
{
frame.FrameDataChunks.Add(idat);
}
else if (chunk is FdAtChunk fdAt)
{
if (firstFrame) // IDATの後の最初のfdATは新しいフレームの始まり
{
if (frame.FrameControl != null) yield return frame;
frame = new AnimationFrame(); // 新しいフレーム
firstFrame = false;
}
frame.FrameDataChunks.Add(fdAt);
}
}
if (frame.FrameControl != null) yield return frame;
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static PngFile Read(Stream stream) => new PngReader(stream).Read();
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="stream"></param>
public void Write(Stream stream) => new PngWriter(stream).Write(this);
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
// PngReader.cs
public class PngReader
{
// ---------------------------------------------------------------------------
private readonly BinaryReader _reader;
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="stream"></param>
public PngReader(Stream stream)
{
_reader = new BinaryReader(stream, Encoding.ASCII, true);
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidDataException"></exception>
public PngFile Read()
{
var pngFile = new PngFile();
// シグネチャ検証
var signature = _reader.ReadBytes(8);
if (!signature.SequenceEqual(PngHelper.PngSignature))
throw new InvalidDataException("Not a valid PNG file.");
// チャンク読み込み
while (_reader.BaseStream.Position < _reader.BaseStream.Length)
{
var length = PngHelper.ReadUInt32BigEndian(_reader);
var typeBytes = _reader.ReadBytes(4);
var type = Encoding.ASCII.GetString(typeBytes);
var data = _reader.ReadBytes((int)length);
var crc = PngHelper.ReadUInt32BigEndian(_reader);
if (Crc32.Calculate(typeBytes, data) != crc)
throw new InvalidDataException($"CRC error in {type} chunk.");
Chunk chunk; // chunk変数をswitch文の外で宣言
switch (type)
{
case "IHDR":
chunk = new IhdrChunk(data);
break;
case "IDAT":
chunk = new IdatChunk(data);
break;
case "IEND":
chunk = new IendChunk(data);
break;
case "acTL":
chunk = new AcTlChunk(data);
break;
case "fcTL":
chunk = new FcTlChunk(data);
break;
case "fdAT":
chunk = new FdAtChunk(data);
break;
case "cICP":
chunk = new CICPChunk(data);
break;
// 将来的に mDCV や cLLI チャンクを追加する場合は、ここにcaseを追加します
// case "mDCV":
// chunk = new MDVCChunk(data);
// break;
// case "cLLI":
// chunk = new CLLIChunk(data);
// break;
case "eXIf":
chunk = new ExifChunk(data);
break;
// 他の既知のチャンクもここに追加できます
default:
// 上記のいずれにも一致しない場合 (未知のチャンク)
chunk = new GenericChunk(type, data);
break;
}
pngFile.Chunks.Add(chunk);
pngFile.Chunks.Add(chunk);
if (type == "IEND") break;
}
return pngFile;
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
// 未知または未実装のチャンクを保持するためのクラス
private class GenericChunk : Chunk
{
public GenericChunk(string type, byte[] data) : base(type, data) { }
protected override void ParseData(BinaryReader reader) { }
protected override void WriteData(BinaryWriter writer) { writer.Write(Data); }
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
// PngWriter.cs
public class PngWriter
{
// ---------------------------------------------------------------------------
private readonly BinaryWriter _writer;
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="stream"></param>
public PngWriter(Stream stream)
{
_writer = new BinaryWriter(stream, Encoding.ASCII, true);
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="pngFile"></param>
public void Write(PngFile pngFile)
{
_writer.Write(PngHelper.PngSignature);
foreach (var chunk in pngFile.Chunks)
{
chunk.WriteTo(_writer);
}
}
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
public static void Sample()
{
string filePath = "path/to/your/image.png";
// PNGファイルの読み込み
PngFile pngFile;
using (var fs = File.OpenRead(filePath))
{
pngFile = PngFile.Read(fs);
}
Console.WriteLine("PNG file loaded.");
var ihdr = pngFile.Chunks.OfType<IhdrChunk>().First();
Console.WriteLine($" Dimension: {ihdr.Width}x{ihdr.Height}");
Console.WriteLine($" Bit Depth: {ihdr.BitDepth}, Color Type: {ihdr.ColorType}");
// Exif情報の確認
var exifChunk = pngFile.Chunks.OfType<ExifChunk>().FirstOrDefault();
if (exifChunk != null)
{
Console.WriteLine($" Found eXIf chunk with {exifChunk.Data.Length} bytes of data.");
// ここで外部ライブラリ(例: MetadataExtractor)にexifChunk.Dataを渡して詳細を解析できる
}
// HDR情報の確認
var cicpChunk = pngFile.Chunks.OfType<CICPChunk>().FirstOrDefault();
if (cicpChunk != null)
{
Console.WriteLine(" Found HDR-related cICP chunk:");
Console.WriteLine($" Color Primaries: {cicpChunk.ColorPrimaries}");
Console.WriteLine($" Transfer Function: {cicpChunk.TransferFunction}");
Console.WriteLine($" Full Range: {cicpChunk.VideoFullRangeFlag == 1}");
}
// APNG情報の確認
if (pngFile.IsAnimated)
{
var actl = pngFile.Chunks.OfType<AcTlChunk>().First();
Console.WriteLine($" This is an animated PNG.");
Console.WriteLine($" Frames: {actl.NumFrames}, Plays: {actl.NumPlays}");
var frames = pngFile.GetAnimationFrames().ToList();
Console.WriteLine($" Parsed {frames.Count} animation frames.");
foreach (var frame in frames.Take(3)) // 最初の3フレームの情報表示
{
var fc = frame.FrameControl;
Console.WriteLine($" - Frame Seq:{fc.SequenceNumber}, {fc.Width}x{fc.Height} @ ({fc.XOffset},{fc.YOffset}) Delay: {fc.DelayNum}/{fc.DelayDen}s");
}
}
// PNGファイルの保存 (例として読み込んだものをそのまま別名で保存)
string outputPath = "path/to/your/output.png";
using (var fs = File.Create(outputPath))
{
pngFile.Write(fs);
}
Console.WriteLine($"File saved to {outputPath}");
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
/// <summary>
/// PNG仕様で定義されているCRC-32チェックサムを計算するクラス。
/// </summary>
public class Crc32
{
// ---------------------------------------------------------------------------
//
private static readonly uint[] table;
// ---------------------------------------------------------------------------
/// <summary>
/// 静的コンストラクタ。CRC計算用のテーブルを初回アクセス時に一度だけ生成する。
/// </summary>
static Crc32()
{
table = new uint[256];
const uint poly = 0xEDB88320; // PNGで使用されるCRC-32の多項式
for (uint i = 0; i < 256; i++)
{
uint c = i;
for (int k = 0; k < 8; k++)
{
if ((c & 1) != 0)
c = poly ^ (c >> 1);
else
c = c >> 1;
}
table[i] = c;
}
}
// ---------------------------------------------------------------------------
// 現在のCRC計算値を保持するインスタンス変数
private uint crc = 0xFFFFFFFF;
// ---------------------------------------------------------------------------
/// <summary>
/// CRCの計算をバイト配列で更新する。
/// </summary>
/// <param name="buffer">計算に含めるバイト配列。</param>
/// <param name="offset">配列内の開始オフセット。</param>
/// <param name="length">計算に含めるバイト数。</param>
public void Update(byte[] buffer, int offset, int length)
{
for (int i = 0; i < length; i++)
{
crc = table[(crc ^ buffer[offset + i]) & 0xFF] ^ (crc >> 8);
}
}
// ---------------------------------------------------------------------------
/// <summary>
/// 最終的なCRC値を取得する。
/// </summary>
/// <returns>計算されたCRC-32値。</returns>
public uint GetValue()
{
// 仕様に従い、最後にビットを反転する
return crc ^ 0xFFFFFFFF;
}
// ---------------------------------------------------------------------------
/// <summary>
/// 静的ヘルパーメソッド。指定されたチャンクタイプとデータから直接CRC値を計算する。
/// </summary>
/// <param name="chunkType">4バイトのチャンクタイプ。</param>
/// <param name="data">チャンクのデータ部分。</param>
/// <returns>計算されたCRC-32値。</returns>
public static uint Calculate(byte[] chunkType, byte[] data)
{
if (chunkType == null || chunkType.Length != 4)
{
throw new ArgumentException("Chunk type must be 4 bytes.", nameof(chunkType));
}
var crcInstance = new Crc32();
// チャンクタイプとデータの両方でCRCを計算する
crcInstance.Update(chunkType, 0, 4);
if (data != null && data.Length > 0)
{
crcInstance.Update(data, 0, data.Length);
}
return crcInstance.GetValue();
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
}


コメント