2012-10-03 11 views
51

Theo như tôi có thể nói, có hai cách để sao chép một bitmap.Sự khác nhau giữa Bitmap.Clone() và Bitmap (Bitmap) mới là gì?

Bitmap.Clone()

Bitmap A = new Bitmap("somefile.png"); 
Bitmap B = (Bitmap)A.Clone(); 

Bitmap mới()

Bitmap A = new Bitmap("somefile.png"); 
Bitmap B = new Bitmap(A); 

Làm thế nào để những cách tiếp cận khác nhau? Tôi đặc biệt quan tâm đến sự khác biệt về bộ nhớ và luồng.

+3

Tôi đã có một trường hợp hồ sơ tôi đã đọc là 1 bit trên mỗi điểm ảnh tập tin TIFF. 'bitmap mới (A)' trả về bitmap 32 bit cho mỗi pixel, trong khi '(Bitmap) A.Clone()' vẫn là 1 bit trên mỗi pixel. Vì tôi đã nhúng hình ảnh vào một tệp PDF để gửi email sau này, việc giữ hình ảnh ở mức 1 bit là quan trọng. @Aelios @HansPassant – gmlobdell

Trả lời

49

Đó là sự khác biệt chung giữa bản sao "sâu" và "nông", cũng là vấn đề với giao diện IClonable gần như không được chấp nhận. Phương thức Clone() tạo ra một đối tượng Bitmap mới nhưng dữ liệu pixel được chia sẻ với đối tượng bitmap gốc. Phương thức khởi tạo của Bitmap (Image) cũng tạo ra một đối tượng Bitmap mới nhưng một đối tượng có bản sao dữ liệu pixel riêng của nó.

Sử dụng bản sao() là rất hiếm khi hữu ích. Rất nhiều câu hỏi về nó tại SO, nơi các lập trình viên hy vọng rằng Clone() tránh những rắc rối điển hình với bitmap, khóa trên tập tin mà từ đó nó được nạp. Nó không. Chỉ sử dụng Clone() khi bạn chuyển một tham chiếu đến mã để phân phối bitmap và bạn không muốn mất đối tượng.

+4

Đồng ý. Chúng tôi đã sử dụng Clone() trong trường hợp chúng ta cần sử dụng có cùng Bitmap được sử dụng (chưa sửa đổi) ở nhiều nơi, nhưng chúng tôi muốn giảm số lượng bộ nhớ được sử dụng bởi các bản sao. Một điều tôi không biết là nếu bạn sửa đổi một trong các dòng vô tính (tức là SetPixel), nếu điều đó làm cho tất cả dữ liệu pixel được chia sẻ bị sửa đổi hoặc nếu nó làm cho dữ liệu pixel được sửa đổi phân bổ dữ liệu pixel của riêng nó). –

+0

@ MattSmith, dữ liệu sẽ được sao chép sau lệnh khóa, ngay cả với cờ ReandOnly. – Pedro77

+0

@HansPassant, By * "disposes" *, bạn có nghĩa là, * "gọi phương thức' .Dispose() '* khi bạn nói điều này: *" Chỉ sử dụng Clone() khi bạn chuyển một tham chiếu đến mã phân phối bitmap và bạn không muốn mất đối tượng. "* – kdbanman

87

Đọc các câu trả lời trước, tôi lo lắng rằng dữ liệu pixel sẽ được chia sẻ giữa các phiên bản nhân bản của Bitmap. Vì vậy, tôi đã thực hiện một số thử nghiệm để tìm ra sự khác biệt giữa Bitmap.Clone()new Bitmap().

Bitmap.Clone() giữ file gốc khóa:

Bitmap original = new Bitmap("Test.jpg"); 
    Bitmap clone = (Bitmap) original.Clone(); 
    original.Dispose(); 
    File.Delete("Test.jpg"); // Will throw System.IO.IOException 

Sử dụng new Bitmap(original) thay vào đó sẽ mở khóa các tập tin sau khi original.Dispose(), và ngoại lệ sẽ không được ném. Sử dụng lớp Graphics để sửa đổi các bản sao (được tạo ra với .Clone()) sẽ không sửa đổi bản gốc:

Bitmap original = new Bitmap("Test.jpg"); 
    Bitmap clone = (Bitmap) original.Clone(); 
    Graphics gfx = Graphics.FromImage(clone); 
    gfx.Clear(Brushes.Magenta); 
    Color c = original.GetPixel(0, 0); // Will not equal Magenta unless present in the original 

Tương tự như vậy, sử dụng phương pháp LockBits mang lại khối bộ nhớ khác nhau cho các bản sao gốc và:

Bitmap original = new Bitmap("Test.jpg"); 
    Bitmap clone = (Bitmap) original.Clone(); 
    BitmapData odata = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadWrite, original.PixelFormat); 
    BitmapData cdata = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadWrite, clone.PixelFormat); 
    Assert.AreNotEqual(odata.Scan0, cdata.Scan0); 

Các kết quả giống nhau với cả hai số object ICloneable.Clone()Bitmap Bitmap.Clone(Rectangle, PixelFormat).

Tiếp theo, tôi đã thử một số điểm chuẩn đơn giản bằng cách sử dụng mã sau đây.

Lưu 50 bản sao trong danh sách mất 6.2 giây và kết quả là 1,7 sử dụng bộ nhớ GB (ảnh gốc là 24 bpp và 3456 x 2400 pixel = 25 MB):

Bitmap original = new Bitmap("Test.jpg"); 
    long mem1 = Process.GetCurrentProcess().PrivateMemorySize64; 
    Stopwatch timer = Stopwatch.StartNew(); 

    List<Bitmap> list = new List<Bitmap>(); 
    Random rnd = new Random(); 
    for(int i = 0; i < 50; i++) 
    { 
    list.Add(new Bitmap(original)); 
    } 

    long mem2 = Process.GetCurrentProcess().PrivateMemorySize64; 
    Debug.WriteLine("ElapsedMilliseconds: " + timer.ElapsedMilliseconds); 
    Debug.WriteLine("PrivateMemorySize64: " + (mem2 - mem1)); 

Sử dụng Clone() thay vào đó tôi có thể lưu trữ 1 000 000 bản trong danh sách trong vòng 0,7 giây và sử dụng 0,9 GB. Đúng như dự đoán, Clone() là rất nhẹ so với new Bitmap():

for(int i = 0; i < 1000000; i++) 
    { 
    list.Add((Bitmap) original.Clone()); 
    } 

nhái sử dụng phương pháp Clone() là copy-on-write. Ở đây tôi thay đổi một pixel ngẫu nhiên thành màu ngẫu nhiên trên bản sao.Thao tác này dường như kích hoạt một bản sao của tất cả các dữ liệu pixel từ bản gốc, bởi vì chúng tôi bây giờ trở lại ở 7,8 giây và 1,6 GB:

Random rnd = new Random(); 
    for(int i = 0; i < 50; i++) 
    { 
    Bitmap clone = (Bitmap) original.Clone(); 
    clone.SetPixel(rnd.Next(clone.Width), rnd.Next(clone.Height), Color.FromArgb(rnd.Next(0x1000000))); 
    list.Add(clone); 
    } 

Chỉ cần tạo một đối tượng Graphics từ hình ảnh sẽ không kích hoạt các bản sao:

for(int i = 0; i < 50; i++) 
    { 
    Bitmap clone = (Bitmap) original.Clone(); 
    Graphics.FromImage(clone).Dispose(); 
    list.Add(clone); 
    } 

Bạn phải vẽ thứ gì đó bằng cách sử dụng đối tượng Graphics để kích hoạt bản sao. Cuối cùng, sử dụng LockBits mặt khác, sẽ sao chép các dữ liệu ngay cả khi ImageLockMode.ReadOnly được quy định:

for(int i = 0; i < 50; i++) 
    { 
    Bitmap clone = (Bitmap) original.Clone(); 
    BitmapData data = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadOnly, clone.PixelFormat); 
    clone.UnlockBits(data); 
    list.Add(clone); 
    } 
+5

Rất đẹp bài đăng! – Pedro77

+1

Vì vậy, phương pháp nào là tốt nhất để có được bản sao đầy đủ của hình ảnh và tất cả dữ liệu? – Don

+0

Nếu bạn cần một bản sao riêng biệt, tôi sẽ sử dụng Bitmap mới(). Điều này sẽ không giữ tập tin khóa trên tập tin gốc và thời gian cpu và bộ nhớ cần thiết sẽ được sử dụng tại nơi sao chép, không phải ở nơi bạn bắt đầu sửa đổi bản sao. Nhưng nếu bạn không chắc chắn liệu bản sao sẽ được sửa đổi hay không, .Clone() có lẽ là một lựa chọn tốt hơn. – Anlo