Замена белого - одна из возможностей, но она не очень хороша. Основываясь на изображениях, которые у вас есть, идеальное решение для этого - получить шаблон с правильной альфа-версией, а затем закрасить видимые черные линии поверх него. Это на самом деле процесс с еще несколькими шагами:
- Извлечение альфа из изображения формы ноги
- Извлечение черных линий из изображения формы ноги
- Применение альфа к изображению шаблона
- закрасьте черные линии поверх изображения шаблона с альфа-коррекцией
Для этого я бы извлек данные обоих изображений в виде байтовых массивов ARGB Это означает, что каждый пиксель составляет четыре байта в порядке B, G, R, A. Затем для каждого пикселя мы просто копируем альфа-байт из изображения формы ноги в альфа-байт изображения шаблона, так что вы в конечном итоге с изображением шаблона, с примененной к нему прозрачностью формы лапки.
Теперь в новом байтовом массиве того же размера, который начинается с чистых 00 байтов (то есть, поскольку A, R, G и B - все ноль, прозрачный черный), мы строим черную линию. Пиксели можно считать «черными», если они не белые, а видимые. Таким образом, идеальный результат, в том числе плавное затухание, состоит в том, чтобы настроить альфа этого нового изображения на минимальное значение альфа и инверсию яркости. Так как это оттенки серого, любой из R, G, B подойдет для яркости. Чтобы получить обратное значение в виде байта, мы просто берем (255 - brightness)
.
Обратите внимание: если вам нужно применить это к загрузке изображений, вы, вероятно, захотите извлечь байты, размеры и шаг шаблона ноги. image только один раз заранее, и держите их в переменных, чтобы дать процессу альфа-замены. Фактически, поскольку изображение черных линий также не изменится, шаг предварительной обработки для генерации должен ускорить процесс.
public static void BakeImages(String whiteFilePath, String materialsFolder, String resultFolder)
{
Int32 width;
Int32 height;
Int32 stride;
// extract bytes of shape & alpha image
Byte[] shapeImageBytes;
using (Bitmap shapeImage = new Bitmap(whiteFilePath))
{
width = shapeImage.Width;
height = shapeImage.Height;
// extract bytes of shape & alpha image
shapeImageBytes = GetImageData(shapeImage, out stride, PixelFormat.Format32bppArgb);
}
using (Bitmap blackImage = ExtractBlackImage(shapeImageBytes, width, height, stride))
{
//For every material image, calls the fusion method below.
foreach (String materialImagePath in Directory.GetFiles(materialsFolder))
{
using (Bitmap patternImage = new Bitmap(materialImagePath))
using (Bitmap result = ApplyAlphaToImage(shapeImageBytes, width, height, stride, patternImage))
{
if (result == null)
continue;
// paint black lines image onto alpha-adjusted pattern image.
using (Graphics g = Graphics.FromImage(result))
g.DrawImage(blackImage, 0, 0);
result.Save(Path.Combine(resultFolder, Path.GetFileNameWithoutExtension(materialImagePath) + ".png"), ImageFormat.Png);
}
}
}
}
Изображение черных линий:
public static Bitmap ExtractBlackImage(Byte[] shapeImageBytes, Int32 width, Int32 height, Int32 stride)
{
// Create black lines image.
Byte[] imageBytesBlack = new Byte[shapeImageBytes.Length];
// Line start offset is set to 3 to immediately get the alpha component.
Int32 lineOffsImg = 3;
for (Int32 y = 0; y < height; y++)
{
Int32 curOffs = lineOffsImg;
for (Int32 x = 0; x < width; x++)
{
// copy either alpha or inverted brightness (whichever is lowest)
// from the shape image onto black lines image as alpha, effectively
// only retaining the visible black lines from the shape image.
// I use curOffs - 1 (red) because it's the simplest operation.
Byte alpha = shapeImageBytes[curOffs];
Byte invBri = (Byte) (255 - shapeImageBytes[curOffs - 1]);
imageBytesBlack[curOffs] = Math.Min(alpha, invBri);
// Adjust offset to next pixel.
curOffs += 4;
}
// Adjust line offset to next line.
lineOffsImg += stride;
}
// Make the black lines images out of the byte array.
return BuildImage(imageBytesBlack, width, height, stride, PixelFormat.Format32bppArgb);
}
Обработка для применения прозрачности изображения ноги к изображению шаблона:
public static Bitmap ApplyAlphaToImage(Byte[] alphaImageBytes, Int32 width, Int32 height, Int32 stride, Bitmap texture)
{
Byte[] imageBytesPattern;
if (texture.Width != width || texture.Height != height)
return null;
// extract bytes of pattern image. Stride should be the same.
Int32 patternStride;
imageBytesPattern = ImageUtils.GetImageData(texture, out patternStride, PixelFormat.Format32bppArgb);
if (patternStride != stride)
return null;
// Line start offset is set to 3 to immediately get the alpha component.
Int32 lineOffsImg = 3;
for (Int32 y = 0; y < height; y++)
{
Int32 curOffs = lineOffsImg;
for (Int32 x = 0; x < width; x++)
{
// copy alpha from shape image onto pattern image.
imageBytesPattern[curOffs] = alphaImageBytes[curOffs];
// Adjust offset to next pixel.
curOffs += 4;
}
// Adjust line offset to next line.
lineOffsImg += stride;
}
// Make a image out of the byte array, and return it.
return BuildImage(imageBytesPattern, width, height, stride, PixelFormat.Format32bppArgb);
}
Вспомогательная функция для извлечения байтов из изображения:
public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride, PixelFormat desiredPixelFormat)
{
Int32 width = sourceImage.Width;
Int32 height = sourceImage.Height;
BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, desiredPixelFormat);
stride = sourceData.Stride;
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
sourceImage.UnlockBits(sourceData);
return data;
}
Вспомогательная функция для создания нового изображения из байтового массива:
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat)
{
Bitmap newImage = new Bitmap(width, height, pixelFormat);
BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
// Get actual data width.
Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
Int32 targetStride = targetData.Stride;
Int64 scan0 = targetData.Scan0.ToInt64();
// Copy per line, copying only data and ignoring any possible padding.
for (Int32 y = 0; y < height; ++y)
Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
newImage.UnlockBits(targetData);
return newImage;
}
Результат в моем тестовом инструменте:

Как видите, черные линии сохраняются поверх шаблона .