298 lines
9.9 KiB
C#
298 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using BumpKit;
|
|
using NLog;
|
|
using Resources.Utils;
|
|
|
|
namespace Resources.Image
|
|
{
|
|
public class Writer
|
|
{
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
|
private readonly List<Color> _palette;
|
|
private bool hasTransparentColor;
|
|
private bool hasSemiTransparentColors;
|
|
private bool hasPresiceColors;
|
|
private ImageType imageType;
|
|
|
|
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 Writer(Stream stream)
|
|
{
|
|
_writer = new BinaryWriter(stream);
|
|
_palette = new List<Color>();
|
|
}
|
|
|
|
private byte[] Signature => new byte[] { (byte)'B', (byte)'M', (byte)imageType, 0 };
|
|
|
|
public void Write(Bitmap image)
|
|
{
|
|
_image = image;
|
|
_width = (ushort)image.Width;
|
|
_height = (ushort)image.Height;
|
|
|
|
ExtractPalette();
|
|
|
|
if (hasSemiTransparentColors)
|
|
{
|
|
if (hasPresiceColors)
|
|
{
|
|
imageType = ImageType.Bpp32;
|
|
_bitsPerPixel = 32;
|
|
}
|
|
else
|
|
{
|
|
imageType = ImageType.Bpp24;
|
|
_bitsPerPixel = 24;
|
|
}
|
|
}
|
|
else if (hasTransparentColor)
|
|
{
|
|
if (_bitsPerPixel > 8)
|
|
{
|
|
imageType = ImageType.Bpp24;
|
|
_bitsPerPixel = 24;
|
|
}
|
|
else
|
|
{
|
|
imageType = ImageType.Paletted;
|
|
}
|
|
} else
|
|
{
|
|
if (_bitsPerPixel > 8)
|
|
{
|
|
imageType = ImageType.Bpp16;
|
|
_bitsPerPixel = 16;
|
|
}
|
|
else
|
|
{
|
|
imageType = ImageType.Paletted;
|
|
}
|
|
}
|
|
|
|
if (imageType == ImageType.Paletted) {
|
|
_paletteColors = (ushort)_palette.Count;
|
|
if (hasTransparentColor) _transparency = 1;
|
|
if (_bitsPerPixel > 4 && _bitsPerPixel <8) _bitsPerPixel = 8;
|
|
if (_bitsPerPixel == 3) _bitsPerPixel = 4;
|
|
if (_bitsPerPixel == 0) _bitsPerPixel = 1;
|
|
}
|
|
|
|
_rowLengthInBytes = (ushort)Math.Ceiling(_width * _bitsPerPixel / 8.0);
|
|
|
|
_writer.Write(Signature);
|
|
|
|
WriteHeader();
|
|
|
|
switch (imageType)
|
|
{
|
|
case ImageType.Paletted:
|
|
WritePalette();
|
|
WritePalettedImage();
|
|
break;
|
|
case ImageType.Bpp16:
|
|
Write16BitImage();
|
|
break;
|
|
case ImageType.Bpp24:
|
|
Write24BitImage();
|
|
break;
|
|
case ImageType.Bpp32:
|
|
Write32BitImage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void ExtractPalette()
|
|
{
|
|
Logger.Trace("Extracting palette...");
|
|
|
|
using (var context = _image.CreateUnsafeContext())
|
|
{
|
|
for (var y = 0; y < _height; y++)
|
|
for (var x = 0; x < _width; x++)
|
|
{
|
|
var color = context.GetPixel(x, y);
|
|
if (_palette.Contains(color)) continue;
|
|
|
|
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, A {4:X2}",
|
|
_palette.Count, color.R, color.G, color.B, color.A
|
|
);
|
|
|
|
if (!hasTransparentColor && color.A == 0)
|
|
{
|
|
Logger.Trace("Found fully transaparent color");
|
|
_palette.Insert(0, color);
|
|
hasTransparentColor = true;
|
|
}
|
|
else
|
|
{
|
|
_palette.Add(color);
|
|
}
|
|
|
|
if (!hasSemiTransparentColors && color.A > 0 && color.A < 0xff)
|
|
{
|
|
Logger.Trace("Found semi transaparent color");
|
|
hasSemiTransparentColors = true;
|
|
}
|
|
|
|
if (!hasPresiceColors && ((color.R & 0x7) > 0 || (color.G & 0x3) > 0 || (color.B & 0x3) > 0))
|
|
{
|
|
Logger.Trace("Found presice color");
|
|
hasPresiceColors = true;
|
|
}
|
|
}
|
|
}
|
|
_bitsPerPixel = (ushort)Math.Ceiling(Math.Log(_palette.Count, 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 WritePalettedImage()
|
|
{
|
|
Logger.Trace("Writing paletted image...");
|
|
|
|
var paletteHash = new Dictionary<Color, byte>();
|
|
byte i = 0;
|
|
foreach (var color in _palette)
|
|
{
|
|
paletteHash[color] = i;
|
|
i++;
|
|
}
|
|
|
|
using (var context = _image.CreateUnsafeContext())
|
|
{
|
|
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 = context.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Write16BitImage()
|
|
{
|
|
Logger.Trace("Writing 16-bit image...");
|
|
using (var context = _image.CreateUnsafeContext())
|
|
{
|
|
for (var y = 0; y < _height; y++)
|
|
{
|
|
var rowData = new byte[_rowLengthInBytes];
|
|
var memoryStream = new MemoryStream(rowData);
|
|
for (var x = 0; x < _width; x++)
|
|
{
|
|
var color = context.GetPixel(x, y);
|
|
|
|
byte first = (byte)((color.R >> 3) |((((color.G >> 2) & 0x7)) << 5));
|
|
byte second = (byte)(color.B | ((color.G >> 5) & 0x7));
|
|
|
|
memoryStream.WriteByte(first);
|
|
memoryStream.WriteByte(second);
|
|
}
|
|
_writer.Write(rowData);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Write24BitImage()
|
|
{
|
|
Logger.Trace("Writing 24-bit image...");
|
|
using (var context = _image.CreateUnsafeContext())
|
|
{
|
|
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 = context.GetPixel(x, y);
|
|
|
|
bitWriter.Write((byte)(0xff - color.A));
|
|
bitWriter.WriteBits((ulong)color.B >> 3, 5);
|
|
bitWriter.WriteBits((ulong)color.G >> 2, 6);
|
|
bitWriter.WriteBits((ulong)color.R >> 3, 5);
|
|
}
|
|
bitWriter.Flush();
|
|
_writer.Write(rowData);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Write32BitImage()
|
|
{
|
|
Logger.Trace("Writing 32-bit image...");
|
|
using (var context = _image.CreateUnsafeContext())
|
|
{
|
|
for (var y = 0; y < _height; y++)
|
|
{
|
|
var rowData = new byte[_rowLengthInBytes];
|
|
var memoryStream = new MemoryStream(rowData);
|
|
for (var x = 0; x < _width; x++)
|
|
{
|
|
var color = context.GetPixel(x, y);
|
|
|
|
memoryStream.WriteByte(color.R);
|
|
memoryStream.WriteByte(color.G);
|
|
memoryStream.WriteByte(color.B);
|
|
memoryStream.WriteByte((byte)(0xff - color.A));
|
|
}
|
|
_writer.Write(rowData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |