Skip to content

Commit

Permalink
Rework libgdiplus hacks
Browse files Browse the repository at this point in the history
  • Loading branch information
nickgal committed Jul 8, 2023
1 parent de2bfb1 commit 79f7f1e
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 69 deletions.
121 changes: 121 additions & 0 deletions LanternExtractor/Infrastructure/EqBmp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace LanternExtractor.Infrastructure
{
/// <summary>
/// Wrapper over Bitmap with various hacks to make libgdiplus
/// consistently create transparent pngs on MacOS & Linux
/// </summary>
public class EqBmp
{
private static readonly bool _needsGdipHacks = Environment.OSVersion.Platform == PlatformID.MacOSX ||
Environment.OSVersion.Platform == PlatformID.Unix;
private static bool _hasCheckedForPaletteFlagsField;
private static System.Reflection.FieldInfo _paletteFlagsField = null;

public PixelFormat PixelFormat => _bitmap.PixelFormat;

private readonly Bitmap _bitmap;
private readonly ColorPalette _palette;

public EqBmp(Stream stream)
{
SetPaletteFlagsField();

_bitmap = new Bitmap(stream);
_palette = _bitmap.Palette;
}

public void WritePng(string outputFilePath)
{
_bitmap.Save(outputFilePath, ImageFormat.Png);
}

public void MakeMagentaTransparent()
{
_bitmap.MakeTransparent(Color.Magenta);
if (_needsGdipHacks)
{
// https://github.com/ mono/libgdiplus/commit/bf9a1440b7bfea704bf2cb771f5c2b5c09e7bcfa
_bitmap.MakeTransparent(Color.FromArgb(0, Color.Magenta));
}
}

public void MakePaletteTransparent(int transparentIndex)
{
if (_needsGdipHacks)
{
// https://github.com/mono/libgdiplus/issues/702
_paletteFlagsField?.SetValue(_palette, _palette.Flags | (int)PaletteFlags.HasAlpha);
}

var transparentColor = GetTransparentPaletteColor();
_palette.Entries[transparentIndex] = transparentColor;
_bitmap.Palette = _palette;

if (_needsGdipHacks)
{
// Due to a bug with the libgdiplus implementation of System.Drawing, setting a color palette
// entry to transparent does not work. The workaround is to ensure that the transparent
// key is unique and then use MakeTransparent()
_bitmap.MakeTransparent(transparentColor);
}
}

private Color GetTransparentPaletteColor()
{
var transparencyColor = Color.FromArgb(0, 0, 0, 0);

if (!_needsGdipHacks)
{
return transparencyColor;
}

var random = new Random();
var foundUnique = false;

while (!foundUnique)
{
foundUnique = _palette.Entries.All(e => e != transparencyColor);
transparencyColor = Color.FromArgb(0, random.Next(256), random.Next(256), random.Next(256));
}

return transparencyColor;
}

// https://github.com/Robmaister/SharpFont/blob/422bdab059dd8e594b4b061a3b53152e71342ce2/Source/SharpFont.GDI/FTBitmapExtensions.cs
// https://github.com/Robmaister/SharpFont/pull/136
private static void SetPaletteFlagsField()
{
if (!_needsGdipHacks || _hasCheckedForPaletteFlagsField)
{
return;
}

_hasCheckedForPaletteFlagsField = true;

// The field needed may be named "flags" or "_flags", dependin on the version of Mono. To be thorough, check for the first Name that contains "lags".
var fields = typeof(ColorPalette).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

for (int i = 0; i < fields.Length; i++)
{
if (fields[i].Name.Contains("lags"))
{
_paletteFlagsField = fields[i];
break;
}
}
}

enum PaletteFlags
{
HasAlpha = 0x0001,
GrayScale = 0x0002,
HalfTone = 0x0004,
}
}
}
82 changes: 13 additions & 69 deletions LanternExtractor/Infrastructure/ImageWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,19 @@ public static void WriteImageAsPng(byte[] bytes, string filePath, string fileNam
private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName, bool isMasked, bool rotate,
ILogger logger)
{
var byteStream = new MemoryStream(bytes);

if (string.IsNullOrEmpty(filePath))
{
return;
}

Directory.CreateDirectory(filePath);

Bitmap image;
EqBmp image;
var byteStream = new MemoryStream(bytes);

try
{
image = new Bitmap(byteStream);
image = new EqBmp(byteStream);
}
catch (Exception e)
{
Expand All @@ -60,76 +59,21 @@ private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName
fileName = "canwall1.png";
}

Bitmap cloneBitmap;

if (isMasked)
switch (image.PixelFormat)
{
cloneBitmap = image.Clone(new Rectangle(0, 0, image.Width, image.Height),
PixelFormat.Format8bppIndexed);

int paletteIndex = GetPaletteIndex(fileName);
var palette = cloneBitmap.Palette;

if (Environment.OSVersion.Platform != PlatformID.MacOSX &&
Environment.OSVersion.Platform != PlatformID.Unix)
{
palette.Entries[paletteIndex] = Color.FromArgb(0, 0, 0, 0);
cloneBitmap.Palette = palette;
}
else
{
// Due to a bug with the MacOS implementation of System.Drawing, setting a color palette value to
// transparent does not work. The workaround is to ensure that the first palette value (the transparent
// key) is unique and then use MakeTransparent()
Color transparencyColor = palette.Entries[paletteIndex];
bool isUnique = false;

while (!isUnique)
{
isUnique = true;

for (var i = 1; i < cloneBitmap.Palette.Entries.Length; i++)
{
Color paletteValue = cloneBitmap.Palette.Entries[i];

if (paletteValue == transparencyColor)
{
Random random = new Random();
transparencyColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
isUnique = false;
break;
}
}
}

palette.Entries[paletteIndex] = transparencyColor;
cloneBitmap.Palette = palette;
cloneBitmap.MakeTransparent(transparencyColor);

// For some reason, this now has to be done to ensure the pixels are actually set to transparent
// Another head scratching MacOS bug
for (int i = 0; i < cloneBitmap.Width; ++i)
case PixelFormat.Format8bppIndexed:
if (isMasked)
{
for (int j = 0; j < cloneBitmap.Height; ++j)
{
if (cloneBitmap.GetPixel(i, j) == transparencyColor)
{
cloneBitmap.SetPixel(i, j, Color.FromArgb(0, 0, 0, 0));
}
}
var paletteIndex = GetPaletteIndex(fileName);
image.MakePaletteTransparent(paletteIndex);
}
}
}
else
{
cloneBitmap = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format32bppArgb);
if (image.PixelFormat != PixelFormat.Format8bppIndexed)
{
cloneBitmap.MakeTransparent(Color.Magenta);
}
break;
default:
image.MakeMagentaTransparent();
break;
}

cloneBitmap.Save(Path.Combine(filePath, fileName), ImageFormat.Png);
image.WritePng(Path.Combine(filePath, fileName));
}

private static void WriteDdsAsPng(byte[] bytes, string filePath, string fileName)
Expand Down

0 comments on commit 79f7f1e

Please sign in to comment.