Skip to content

Commit

Permalink
Czi Color Zone Index Map reader and writer
Browse files Browse the repository at this point in the history
Can read, unpack , pack and write Czi files
  • Loading branch information
GoomiiV2 committed Oct 19, 2024
1 parent 3526d21 commit 398ee5c
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -432,4 +432,5 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# End of https://www.toptal.com/developers/gitignore/api/visualstudio,rider
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,rider
/Tests
183 changes: 183 additions & 0 deletions FauFau/Formats/Czi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.IO;
using Bitter;
using FauFau.Util;
using SharpCompress;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;

namespace FauFau.Formats
{
public class Czi : BinaryWrapper
{
public Header Head;
public List<MipInfo> MipInfos = new ();
public List<CompressedBlock> CompressedBlocks = new ();

public Czi() { }
public Czi(string filePath)
{
Load(filePath);
}

public static Czi CreateMaskCzi(int width, int height)
{
var czi = new Czi();
czi.Head = new Header
{
Magic = "CZIM",
Version = 3,
Width = width,
Height = height,
Unk = 0,
PatternFlags = 0,
NumMipLevels = 0
};

return czi;
}

public void Load(string filePath)
{
var data = File.ReadAllBytes(filePath);
using var bs = new BinaryStream(new MemoryStream(data));

Read(bs);
}

public void Save(string path)
{
using var fs = new FileStream(path, FileMode.Create);
Write(fs);
}

public override void Read(BinaryStream bs)
{
Head = bs.Read.Type<Header>();
MipInfos = bs.Read.TypeList<MipInfo>(Head.NumMipLevels);
CompressedBlocks = bs.Read.TypeList<CompressedBlock>(Head.NumMipLevels);
}

public override void Write(BinaryStream bs)
{
bs.Write.Type(Head);
bs.Write.TypeList(MipInfos);
bs.Write.TypeList(CompressedBlocks);
}

public byte[] GetMipDecompressed(int idx)
{
var mipInfo = MipInfos[idx];
var compressedBlock = CompressedBlocks[idx];

byte[] decompressedData = new byte[mipInfo.Size];
var msIn = new MemoryStream(compressedBlock.Data);
using var zlib = new ZlibStream(msIn, CompressionMode.Decompress);
zlib.ReadFully(decompressedData);

return decompressedData;
}

public void AddMipLevel(ReadOnlySpan<byte> data)
{
var mipInfo = new MipInfo
{
Offset = Head.NumMipLevels > 0 ? MipInfos[Head.NumMipLevels - 1].Offset + MipInfos[Head.NumMipLevels - 1].Size : 0,
Size = data.Length
};

using var inMs = new BinaryStream(new MemoryStream(data.ToArray()));
using var outMs = new MemoryStream();
using var outBs = new BinaryStream(outMs);
outBs.Write.Byte(0x78);
outBs.Write.Byte(0x9c);
Common.Deflate(inMs, outBs, CompressionLevel.Default);
outBs.ByteOrder = BinaryStream.Endianness.BigEndian;
outBs.Write.UInt(Checksum.Adler32(data));
outBs.ByteOrder = BinaryStream.Endianness.LittleEndian;
outBs.Flush();

var outData = outMs.ToArray();

var block = new CompressedBlock()
{
Length = outData.Length,
Data = outData
};

MipInfos.Add(mipInfo);
CompressedBlocks.Add(block);
Head.NumMipLevels++;
}

public class Header : ReadWrite
{
public string Magic;
public int Version;
public int Width;
public int Height;
public byte PatternFlags;
public byte Unk;
public int NumMipLevels;

public void Read(BinaryStream bs)
{
Magic = bs.Read.String(4);
Version = bs.Read.Int();
Width = bs.Read.Int();
Height = bs.Read.Int();
PatternFlags = bs.Read.Byte();
Unk = bs.Read.Byte();
NumMipLevels = bs.Read.Int();
}

public void Write(BinaryStream bs)
{
bs.Write.String(Magic);
bs.Write.Int(Version);
bs.Write.Int(Width);
bs.Write.Int(Height);
bs.Write.Byte(PatternFlags);
bs.Write.Byte(Unk);
bs.Write.Int(NumMipLevels);
}
}

public class MipInfo : ReadWrite
{
public int Offset; // The combined size of all the previous mip infos added
public int Size;

public void Read(BinaryStream bs)
{
Offset = bs.Read.Int();
Size = bs.Read.Int();
}

public void Write(BinaryStream bs)
{
bs.Write.Int(Offset);
bs.Write.Int(Size);
}
}

public class CompressedBlock : ReadWrite
{
public int Length;
public byte[] Data;

public void Read(BinaryStream bs)
{
Length = bs.Read.Int();
Data = bs.Read.ByteArray(Length);
}

public void Write(BinaryStream bs)
{
bs.Write.Int(Length);
bs.Write.ByteArray(Data);
}
}
}
}
12 changes: 12 additions & 0 deletions FauFau/Util/Checksum.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;

Expand All @@ -23,5 +24,16 @@ public static uint FFnv32(byte[] array)
return 33U * (hash ^ (hash >> 17));
}
}

public static uint Adler32(ReadOnlySpan<byte> data)
{
const int mod = 65521;
uint a = 1, b = 0;
foreach (var c in data) {
a = (a + c) % mod;
b = (b + a) % mod;
}
return (b << 16) | a;
}
}
}
45 changes: 45 additions & 0 deletions Tests/CziTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.IO;
using System.Linq;
using FauFau.Formats;

namespace Tests
{
public class CziTests
{

public static void ReadTest()
{
var czi = new Czi("C:\\temp\\FauFau\\00107072_org.czi");
DumpToFolder(czi, "C:\\temp\\FauFau\\00107072_og");

string packDir = "C:\\temp\\FauFau\\00107072_pack";
if (Directory.Exists(packDir)) {
var newCzi = PackFromFolder(2048, 2048, packDir);
newCzi.Save("C:\\temp\\FauFau\\00107072.czi");
}
}

private static void DumpToFolder(Czi czi, string folder)
{
Directory.CreateDirectory(folder);

for (int i = 0; i < czi.Head.NumMipLevels; i++) {
var mipData = czi.GetMipDecompressed(i);
File.WriteAllBytes($"{folder}\\{i}.raw", mipData);
}
}

private static Czi PackFromFolder(int width, int height, string folder)
{
var czi = Czi.CreateMaskCzi(width, height);

var files = Directory.GetFiles(folder).Order();
foreach (var file in files) {
var data = File.ReadAllBytes(file);
czi.AddMipLevel(data);
}

return czi;
}
}
}
2 changes: 2 additions & 0 deletions Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ static void Main(string[] args)
//SDBTests.TestRead();
//SDBTests.TestWriteCustom();
//SDBTests.TestReadCustom();

CziTests.ReadTest();
}
}
}
2 changes: 1 addition & 1 deletion Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>9</LangVersion>
<LangVersion>latestmajor</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 398ee5c

Please sign in to comment.