位图克隆问题

发布于 2024-08-03 12:04:13 字数 814 浏览 2 评论 0原文

考虑此用于加载、修改和保存位图图像的代码:

    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 的正确代码是什么?

Consider this code for loading, modifying and saving a Bitmap image:

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

it runs without any exception.
But consider this one:

    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");
        }
    }

It ends in an ExternalException with this message: "A generic error occurred in GDI+".
What's wrong here? Any kind of lock on opened file? If so, why the first block works? What is the proper code for cloning a System.Drawing.Bitmap while we may need to edit main object or its clone in the memory and still have them both loaded in memory?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

黄昏下泛黄的笔记 2024-08-10 12:04:13

您还可以使用简单的解决方法加载位图而不使用文件锁定:

using (Stream s = File.OpenRead(@"\My Documents\My Pictures\Waterfall.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);

You could also load the bitmap without file locking using the simple workaround:

using (Stream s = File.OpenRead(@"\My Documents\My Pictures\Waterfall.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);
素年丶 2024-08-10 12:04:13

是的,当加载第一个位图对象时,文件被锁定,因此同一文件的 bmpClone.Save() 失败,因为存在逻辑死锁。

按文件名打开位图时,该文件在位图的整个生命周期中都会被锁定。如果您使用流,则流必须保持打开状态。

更新

如果您希望内存中有两个位图以便在您所在方法的范围之外使用,那么您将不会使用 using 块。

从文件创建第一个图像,然后克隆它。在整个 UI 生命周期中根据需要使用它们,但请确保在不再需要它们时使用 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();

Yes, the file is locked when the first bitmap object is loaded and thus bmpClone.Save() to the same file fails because you have a logical deadlock.

When opening Bitmaps by filename, the file is locked throughout the life of the Bitmap. If you use a stream, the stream must remain open.

Update:

If you wish to have two bitmaps in memory for use outside of the scope of the method you are in, then you won't be using a using block.

Create the first image from file, and then clone it. Utilize them as needed throughout your UI lifecycle but make sure you clean them up using Dispose() when they are no longer needed so that underlying resources are released.

Also, from MSDN:

Saving the image to the same file it
was constructed from is not allowed
and throws an exception

That's pretty awkward. If the object created using clone() keeps information on the image source (e.g. a handle on the original file) or you can't otherwise unlock the file while using the Bitmap instances, then you'll probably have to either save to a new file, or open from a temporary copy of the original.

Try this instead:

// ... 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();
月亮邮递员 2024-08-10 12:04:13

这就是我复制位图的方式:

[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 ##

图像尺寸:{Width=1024,Height=1024}。
图像像素格式:Format8bppIndexed。
Bitmap.Clone():0,00 毫秒(不是 DeepCopy...相同的像素数据 - 看这里
Bitmap.Clone() + RotateFlip(进行深层复制):2,02 ms
KernellDllCopyBitmap:0.52 毫秒(最好的!)
MarshalCopyBitmap:2,21 毫秒

This is how I copy Bitmaps:

[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 ##

Image Size: {Width=1024, Height=1024}.
Image PixelFormat: Format8bppIndexed.
Bitmap.Clone(): 0,00 ms (Not a DeepCopy... Same pixel data - look here)
Bitmap.Clone() + RotateFlip (to guet a deep copy): 2,02 ms
KernellDllCopyBitmap: 0,52 ms (THE BEST!)
MarshalCopyBitmap: 2,21 ms

白云不回头 2024-08-10 12:04:13

这是我的虚荣方法:

    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%(取决于位图大小......)

Here is my vanity method:

    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% faster (depending on bitmap size...)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文