Created
August 12, 2023 13:22
-
-
Save smourier/87545e8ad322b250407e022216a45b6e to your computer and use it in GitHub Desktop.
save .ANI (doesn't work, not sure why)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void SaveAsANI(Image inputImage, string outputFilePath) | |
{ | |
ArgumentNullException.ThrowIfNull(inputImage); | |
ArgumentNullException.ThrowIfNull(outputFilePath); | |
using var stream = new FileStream(outputFilePath, FileMode.Create); | |
SaveAsANI(inputImage, stream); | |
} | |
public static void SaveAsANI(Image inputImage, Stream outputStream) | |
{ | |
ArgumentNullException.ThrowIfNull(inputImage); | |
ArgumentNullException.ThrowIfNull(outputStream); | |
var dimension = new FrameDimension(inputImage.FrameDimensionsList[0]); | |
var frameCount = inputImage.GetFrameCount(dimension); | |
// see https://en.wikipedia.org/wiki/Resource_Interchange_File_Format on RIFF format | |
// see https://www.gdgsoft.com/anituner/help/aniformat.htm for example on .ANI format | |
using var writer = new BinaryWriter(outputStream, Encoding.ASCII); | |
// riff format | |
writeString("RIFF"); | |
// get pos & write dummy value, we'll rewrite that later | |
var riffSizePos = outputStream.Position; | |
writeInt32(0); // 'RIFF' chunk size | |
writeString("ACON"); // .ani format | |
// header | |
writeString("anih"); | |
writeInt32(36); // 'anih' chunk size | |
writeInt32(36); // cbSizeof | |
writeInt32(frameCount); // cFrames | |
writeInt32(frameCount); // cSteps | |
// unused in .ani case | |
writeInt32(0); // cx | |
writeInt32(0); // cy | |
writeInt32(32); // cBitCount | |
writeInt32(1); // cPlanes | |
writeInt32(3); // jifRate | |
writeInt32(1); // flags (AF_ICON = 1) | |
// some other RIFF chunks | |
writeString("LIST"); | |
// get pos & write dummy value, we'll rewrite that later | |
var listSizePos = outputStream.Position; | |
writeInt32(0); // 'LIST' chunk size | |
writeString("fram"); | |
// note: we use the same buffer for all frames | |
using var ms = new MemoryStream(); | |
for (var i = 0; i < frameCount; i++) | |
{ | |
// get image frame #i | |
inputImage.SelectActiveFrame(dimension, i); | |
writeString("icon"); | |
// get pos & write dummy value, we'll rewrite that later | |
var iconSizePos = outputStream.Position; | |
writeInt32(0); // 'icon' chunk size | |
// get png bytes | |
ms.Position = 0; | |
inputImage.Save(ms, ImageFormat.Png); | |
var beforeIconPos = outputStream.Position; | |
WritePNGCursor(outputStream, (uint)inputImage.Width, (uint)inputImage.Height, ms.GetBuffer(), 0, (int)ms.Length); | |
var afterIconPos = outputStream.Position; | |
// now we can get back to write final icon size | |
outputStream.Position = iconSizePos; | |
writeInt32((int)(afterIconPos - beforeIconPos)); | |
// restore pos | |
outputStream.Position = afterIconPos; | |
} | |
// now we can get back to write final sizes | |
outputStream.Position = riffSizePos; | |
writeInt32((int)(outputStream.Length - riffSizePos - 4)); | |
outputStream.Position = listSizePos; | |
writeInt32((int)(outputStream.Length - listSizePos - 4)); | |
void writeString(string str) => outputStream.Write(Encoding.ASCII.GetBytes(str)); | |
void writeInt32(int i) => outputStream.Write(BitConverter.GetBytes(i)); | |
} | |
// utility methods to write .cur files with PNG format (the easiest) | |
public static void WritePNGCursor(Stream iconFileStream, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue) | |
{ | |
ArgumentNullException.ThrowIfNull(iconFileStream); | |
ArgumentNullException.ThrowIfNull(pngBytes); | |
var startPos = iconFileStream.Position; | |
if (pngBytesCount == int.MaxValue || pngBytesCount < 0) | |
{ | |
pngBytesCount = pngBytes.Length; | |
} | |
var writer = new BinaryWriter(iconFileStream); | |
writer.Write((short)0); // reserved | |
writer.Write((short)2); // image type (2 for .cur) | |
writer.Write((short)1); // count | |
const int maxIconSize = 256; | |
writer.Write(width == maxIconSize ? (byte)0 : (byte)width); | |
writer.Write(height == maxIconSize ? (byte)0 : (byte)height); | |
writer.Write((byte)0); // number of colors in the color palette | |
writer.Write((byte)0); // reserved | |
writer.Write((short)0); // h hotspot | |
writer.Write((short)0); // v hotspot | |
var offsets = iconFileStream.Position; | |
writer.Write(0); // size of image data, we'll rewrite that later | |
writer.Write(0); // offset of data from beginning; | |
var pngOffset = iconFileStream.Position - startPos; | |
writer.Write(pngBytes, pngBytesIndex, pngBytesCount); | |
// save end position | |
var endPos = iconFileStream.Position; | |
iconFileStream.Seek(offsets, SeekOrigin.Begin); | |
writer.Write(pngBytesCount); | |
writer.Write((int)pngOffset); | |
iconFileStream.Position = endPos; // restore end position | |
} | |
public static void WritePNGCursor(string iconFilePath, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue) | |
{ | |
ArgumentNullException.ThrowIfNull(iconFilePath); | |
ArgumentNullException.ThrowIfNull(pngBytes); | |
using var stream = File.OpenWrite(iconFilePath); | |
WritePNGCursor(stream, width, height, pngBytes, pngBytesIndex, pngBytesCount); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment