162 lines
5.2 KiB
C#
162 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using NLog;
|
|
using Resources.Dithering;
|
|
|
|
namespace Resources
|
|
{
|
|
public class ImageWriter
|
|
{
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
|
private static readonly byte[] Signature = {(byte) 'B', (byte) 'M', (byte) 'd', 0};
|
|
private readonly List<Color> _palette;
|
|
|
|
private readonly BinaryWriter _writer;
|
|
|
|
private ushort _bitsPerPixel;
|
|
private ushort _height;
|
|
private Bitmap _image;
|
|
private ushort _paletteColors;
|
|
private ushort _rowLengthInBytes;
|
|
private ushort _transparency;
|
|
private ushort _width;
|
|
|
|
public ImageWriter(Stream stream)
|
|
{
|
|
_writer = new BinaryWriter(stream);
|
|
_palette = new List<Color>();
|
|
}
|
|
|
|
public void Write(Bitmap image)
|
|
{
|
|
_image = image;
|
|
_width = (ushort) image.Width;
|
|
_height = (ushort) image.Height;
|
|
|
|
ApplyDithering();
|
|
ExtractPalette();
|
|
|
|
if (_bitsPerPixel == 3) _bitsPerPixel = 4;
|
|
if (_bitsPerPixel == 0) _bitsPerPixel = 1;
|
|
|
|
if (_bitsPerPixel > 4)
|
|
throw new ArgumentException(
|
|
$"The image has {_bitsPerPixel} bit/pixel and can't be packed used on the watches. Looks like dithering works wincorrectly on the image."
|
|
);
|
|
|
|
_rowLengthInBytes = (ushort) Math.Ceiling(_width * _bitsPerPixel / 8.0);
|
|
|
|
_writer.Write(Signature);
|
|
|
|
WriteHeader();
|
|
WritePalette();
|
|
WriteImage();
|
|
}
|
|
|
|
private void ApplyDithering()
|
|
{
|
|
var clone = new Bitmap(_image.Width, _image.Height, PixelFormat.Format32bppArgb);
|
|
using (var gr = Graphics.FromImage(clone))
|
|
{
|
|
gr.DrawImage(_image, new Rectangle(0, 0, clone.Width, clone.Height));
|
|
}
|
|
FloydSteinbergDitherer.Process(clone);
|
|
_image = clone;
|
|
}
|
|
|
|
private void ExtractPalette()
|
|
{
|
|
Logger.Trace("Extracting palette...");
|
|
for (var y = 0; y < _height; y++)
|
|
for (var x = 0; x < _width; x++)
|
|
{
|
|
var color = _image.GetPixel(x, y);
|
|
if (_palette.Contains(color)) continue;
|
|
|
|
if (color.A < 0x80 && _transparency == 0)
|
|
{
|
|
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, Transaparent color",
|
|
_palette.Count, color.R, color.G, color.B
|
|
);
|
|
_palette.Insert(0, color);
|
|
_transparency = 1;
|
|
}
|
|
else
|
|
{
|
|
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}",
|
|
_palette.Count, color.R, color.G, color.B
|
|
);
|
|
_palette.Add(color);
|
|
}
|
|
}
|
|
_paletteColors = (ushort) _palette.Count;
|
|
_bitsPerPixel = (ushort) Math.Ceiling(Math.Log(_paletteColors, 2));
|
|
}
|
|
|
|
private void WriteHeader()
|
|
{
|
|
Logger.Trace("Writing image header...");
|
|
Logger.Trace("Width: {0}, Height: {1}, RowLength: {2}", _width, _height, _rowLengthInBytes);
|
|
Logger.Trace("BPP: {0}, PaletteColors: {1}, Transaparency: {2}",
|
|
_bitsPerPixel, _paletteColors, _transparency
|
|
);
|
|
|
|
_writer.Write(_width);
|
|
_writer.Write(_height);
|
|
_writer.Write(_rowLengthInBytes);
|
|
_writer.Write(_bitsPerPixel);
|
|
_writer.Write(_paletteColors);
|
|
_writer.Write(_transparency);
|
|
}
|
|
|
|
private void WritePalette()
|
|
{
|
|
Logger.Trace("Writing palette...");
|
|
foreach (var color in _palette)
|
|
{
|
|
_writer.Write(color.R);
|
|
_writer.Write(color.G);
|
|
_writer.Write(color.B);
|
|
_writer.Write((byte) 0); // always 0 maybe padding
|
|
}
|
|
}
|
|
|
|
private void WriteImage()
|
|
{
|
|
Logger.Trace("Writing image...");
|
|
|
|
var paletteHash = new Dictionary<Color, byte>();
|
|
byte i = 0;
|
|
foreach (var color in _palette)
|
|
{
|
|
paletteHash[color] = i;
|
|
i++;
|
|
}
|
|
|
|
for (var y = 0; y < _height; y++)
|
|
{
|
|
var rowData = new byte[_rowLengthInBytes];
|
|
var memoryStream = new MemoryStream(rowData);
|
|
var bitWriter = new BitWriter(memoryStream);
|
|
for (var x = 0; x < _width; x++)
|
|
{
|
|
var color = _image.GetPixel(x, y);
|
|
if (color.A < 0x80 && _transparency == 1)
|
|
{
|
|
bitWriter.WriteBits(0, _bitsPerPixel);
|
|
}
|
|
else
|
|
{
|
|
var paletteIndex = paletteHash[color];
|
|
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
|
|
}
|
|
}
|
|
bitWriter.Flush();
|
|
_writer.Write(rowData);
|
|
}
|
|
}
|
|
}
|
|
} |