Проблема с растровым клонированием - PullRequest
8 голосов
/ 23 августа 2009

Рассмотрим этот код для загрузки, изменения и сохранения растрового изображения:

    using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
    {
        bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
        bmp.Save("C:\\test.jpg");
    }

работает без каких-либо исключений. Но рассмотрим это:

    using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
    {
        using (Bitmap bmpClone = (Bitmap)bmp.Clone())
        {
            //You can replace "bmpClone" in the following lines with "bmp",
            //exception occurs anyway                    
            bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);
            bmpClone.Save("C:\\test.jpg");
        }
    }

Заканчивается в ExternalException следующим сообщением: «Произошла общая ошибка в GDI +». Что здесь не так? Любой вид блокировки на открытом файле? Если так, то почему первый блок работает? Каков правильный код для клонирования System.Drawing.Bitmap, в то время как нам, возможно, потребуется отредактировать основной объект или его клон в памяти и все еще загрузить их в память?

Ответы [ 4 ]

5 голосов
/ 08 сентября 2009

Вы также можете загрузить растровое изображение без блокировки файла, используя простой обходной путь:

using (Stream s = File.OpenRead(@"\My Documents\My Pictures\Waterfall.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);
5 голосов
/ 03 мая 2013

Вот как я копирую растровые изображения:

[DllImport("kernel32.dll", EntryPoint = "CopyMemory")]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

public static Bitmap KernellDllCopyBitmap(Bitmap bmp, bool CopyPalette = true)
{
    Bitmap bmpDest = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);

    if (!KernellDllCopyBitmap(bmp, bmpDest, CopyPalette))
        bmpDest = null;

    return bmpDest;
}


/// <summary>
/// Copy bitmap data.
/// Note: bitmaps must have same size and pixel format.
/// </summary>
/// <param name="bmpSrc">Source Bitmap</param>
/// <param name="bmpDest">Destination Bitmap</param>
/// <param name="CopyPalette">Must copy Palette</param>
public static bool KernellDllCopyBitmap(Bitmap bmpSrc, Bitmap bmpDest, bool CopyPalette = false)
{
    bool copyOk = false;
    copyOk = CheckCompatibility(bmpSrc, bmpDest);
    if (copyOk)
    {
        BitmapData bmpDataSrc;
        BitmapData bmpDataDest;

        //Lock Bitmap to get BitmapData
        bmpDataSrc = bmpSrc.LockBits(new Rectangle(0, 0, bmpSrc.Width, bmpSrc.Height), ImageLockMode.ReadOnly, bmpSrc.PixelFormat);
        bmpDataDest = bmpDest.LockBits(new Rectangle(0, 0, bmpDest.Width, bmpDest.Height), ImageLockMode.WriteOnly, bmpDest.PixelFormat);
        int lenght = bmpDataSrc.Stride * bmpDataSrc.Height;

        CopyMemory(bmpDataDest.Scan0, bmpDataSrc.Scan0, (uint)lenght);

        bmpSrc.UnlockBits(bmpDataSrc);
        bmpDest.UnlockBits(bmpDataDest);

        if (CopyPalette && bmpSrc.Palette.Entries.Length > 0)
            bmpDest.Palette = bmpSrc.Palette;
    }
    return copyOk;
}

    public static bool CheckCompatibility(Bitmap bmp1, Bitmap bmp2)
    {
        return ((bmp1.Width == bmp2.Width) && (bmp1.Height == bmp2.Height) && (bmp1.PixelFormat == bmp2.PixelFormat));
    }

## ImageCopyBenchmark ##

Размер изображения: {Ширина = 1024, Высота = 1024}.
PixelFormat изображения: Format8bppIndexed.
Bitmap.Clone (): 0,00 мс (не DeepCopy ... Те же данные пикселей - смотрите здесь )
Bitmap.Clone () + RotateFlip (для создания глубокой копии): 2,02 мс
KernellDllCopyBitmap: 0,52 мс (ЛУЧШИЙ!)
MarshalCopyBitmap: 2,21 мс

5 голосов
/ 23 августа 2009

Да, файл блокируется при загрузке первого растрового объекта, и, таким образом, bmpClone.Save() в тот же файл не удается, потому что у вас есть логическая тупик.

При открытии растровых изображений по имени файла файл блокируется на протяжении всего срока действия растрового изображения. Если вы используете поток, поток должен оставаться открытым.

Обновление

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

Создайте первое изображение из файла, а затем клонируйте его. Используйте их по мере необходимости на протяжении всего жизненного цикла пользовательского интерфейса, но обязательно очищайте их, используя Dispose(), когда они больше не нужны, чтобы высвободить базовые ресурсы.

Также из MSDN:

Сохранение изображения в тот же файл был построен из не допускается и выбрасывает исключение

Это довольно неловко. Если объект, созданный с помощью clone(), хранит информацию об источнике изображения (например, дескриптор исходного файла) или вы не можете разблокировать файл иным образом при использовании экземпляров Bitmap, то вам, вероятно, придется либо сохранить его в новый файл или открыть из временной копии оригинала.

Попробуйте вместо этого:

// ... make a copy of test.jpg called test_temp.jpg

Bitmap bmpOriginal = new Bitmap("C:\\test_temp.jpg"))
Bitmap bmpClone = (Bitmap)bmp.Clone();

// ... do stuff          
bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);

bmpClone.Save("C:\\test.jpg");

// ... cleanup
bmpOriginal.Dispose();
bmpClone.Dispose();
0 голосов
/ 29 ноября 2014

Вот мой метод тщеславия:

    private static unsafe Bitmap DuplicateBitmap(Bitmap inputBitmap)
    {
        byte[] buffer = new byte[inputBitmap.Height * inputBitmap.Width *
                            Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8];  

        fixed (byte* p = buffer)
        {
            BitmapData b1Data = new BitmapData()
            {
                Scan0 = (IntPtr)p,
                Height = inputBitmap.Height,
                Width = inputBitmap.Width,
                PixelFormat = inputBitmap.PixelFormat,
                Stride = inputBitmap.Width * Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8,
            }; 

            inputBitmap.LockBits(new Rectangle(Point.Empty, inputBitmap.Size),
                ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, inputBitmap.PixelFormat, b1Data); // copy out.   

            Bitmap b2 = new Bitmap(b1Data.Width, b1Data.Height, b1Data.Stride, inputBitmap.PixelFormat, b1Data.Scan0);

            inputBitmap.UnlockBits(b1Data); 

            return b2;
        }
    }

10% быстрее (в зависимости от размера растрового изображения ...)

...