Как скопировать растровые пиксели в другие растровые изображения, сохраняя альфа-прозрачность в C #? - PullRequest
4 голосов
/ 17 февраля 2012

Может ли кто-нибудь переписать следующую функцию, чтобы использовать какой-либо оптимизированный механизм?Я почти уверен, что это не тот способ, копировать пиксель за пикселем.

Я читал о AlphaBlend или BitBlt , но яне используется для собственного кода.

public static Bitmap GetAlphaBitmap(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);

    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);

    try
    {
        for (int y = 0; y <= srcData.Height - 1; y++)
        {
            for (int x = 0; x <= srcData.Width - 1; x++)
            {
                Color pixelColor = Color.FromArgb(
                    Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));

                result.SetPixel(x, y, pixelColor);
            }
        }
    }
    finally
    {
        srcBitmap.UnlockBits(srcData);
    }

    return result;
}

ВАЖНОЕ ПРИМЕЧАНИЕ : исходное изображение имеет неправильный формат пикселей (Format32bppRgb), поэтому мне нужно настроить альфа-канал.Это единственный механизм, который работает для меня.

Причина, по которой изображение src имеет неправильный формат пикселей, объясняется здесь .

Я попробовал следующие варианты без удачи:

  • Создание нового изображения и отрисовка изображения src с использованием Graphics.DrawImage from src.Не сохранил альфа.
  • Создание нового изображения с помощью формы Scan0 src.Работает нормально, но имеет проблему, когда GC удаляет образ src (объяснено в этом другом post );

Это единственное решение, которое действительно работает, но я знаю, чтоне оптимально.Мне нужно знать, как это сделать, используя WinAPI или другой оптимальный механизм.

Большое спасибо!

Ответы [ 3 ]

7 голосов
/ 17 февраля 2012

Предполагая, что исходное изображение действительно имеет 32 бита на пиксель, это должно быть достаточно быстрой реализацией с использованием небезопасного кода и указателей.То же самое может быть достигнуто с помощью маршаллинга, хотя при правильном запоминании потеря производительности составляет около 10% -20%.

Использование собственных методов, скорее всего, будет быстрее, но это уже должно быть на несколько порядков быстрее, чем SetPixel.

public unsafe static Bitmap Clone32BPPBitmap(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
    BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);

     int* srcScan0 = (int*)srcData.Scan0;
     int* resScan0 = (int*)resData.Scan0;
     int numPixels = srcData.Stride / 4 * srcData.Height;
     try
     {
         for (int p = 0; p < numPixels; p++)
         {
             resScan0[p] = srcScan0[p];
         }
     }
     finally
     {
         srcBitmap.UnlockBits(srcData);
         result.UnlockBits(resData);
     }

    return result;
}

Вот безопасная версия этого метода с использованием маршаллинга:

public static Bitmap Copy32BPPBitmapSafe(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
    BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);

    Int64 srcScan0 = srcData.Scan0.ToInt64();
    Int64 resScan0 = resData.Scan0.ToInt64();
    int srcStride = srcData.Stride;
    int resStride = resData.Stride;
    int rowLength = Math.Abs(srcData.Stride);
    try
    {
        byte[] buffer = new byte[rowLength];
        for (int y = 0; y < srcData.Height; y++)
        {
            Marshal.Copy(new IntPtr(srcScan0 + y * srcStride), buffer, 0, rowLength);
            Marshal.Copy(buffer, 0, new IntPtr(resScan0 + y * resStride), rowLength);
        }
    }
    finally
    {
        srcBitmap.UnlockBits(srcData);
        result.UnlockBits(resData);
    }

    return result;
}

Редактировать: Ваше исходное изображение имеет отрицательный шаг, что означаетлинии сканирования хранятся в памяти в обратном порядке (только по оси y, строки по-прежнему идут слева направо).Это фактически означает, что .Scan0 возвращает первый пиксель последней строки растрового изображения.

Поэтому я изменил код для копирования по одной строке за раз.

Примечание:Я только изменил безопасный код.Небезопасный код по-прежнему предполагает положительные результаты для обоих изображений!

0 голосов
/ 17 февраля 2012

Утилита класса в моей библиотеке Codeblocks http://codeblocks.codeplex.com позволяет преобразовывать исходное изображение в любое другое изображение с помощью LINQ.

Смотрите этот образец здесь: http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home

Хотя образец преобразует один и тот же формат изображения между источником и местом назначения, вы также можете изменить все вокруг.

Обратите внимание, что я синхронизировал этот код, и он намного быстрее, чем даже небезопасный код для больших изображений, потому что он использует кэшированное полное чтение строки впереди.

0 голосов
/ 17 февраля 2012

Попробуйте метод Растровое клонирование .

...