Cách hiệu quả nhất để thay đổi kích thước bitmap trên Android?


115

Tôi đang xây dựng một ứng dụng xã hội chuyên sâu về hình ảnh, nơi hình ảnh được gửi từ máy chủ đến thiết bị. Khi thiết bị có độ phân giải màn hình nhỏ hơn, tôi cần thay đổi kích thước bitmap trên thiết bị để phù hợp với kích thước hiển thị dự kiến ​​của chúng.

Vấn đề là việc sử dụng createScaledBitmap khiến tôi gặp phải rất nhiều lỗi hết bộ nhớ sau khi thay đổi kích thước một đống hình ảnh thu nhỏ.

Cách hiệu quả nhất để thay đổi kích thước ảnh bitmap trên Android là gì?


7
Máy chủ của bạn có thể không gửi đúng kích thước để bạn tiết kiệm RAM và băng thông của khách hàng không !?
James

2
Điều đó chỉ hợp lệ nếu tôi sở hữu tài nguyên máy chủ, nó có sẵn thành phần tính toán và trong mọi trường hợp, nó có thể dự đoán kích thước chính xác của hình ảnh cho các tỷ lệ khung hình mà nó chưa thấy. Vì vậy, nếu bạn nội dung tài nguyên tải đang từ một bên thứ 3 CDN (như tôi) nó không làm việc :(
Colt McAnlis

Câu trả lời:


168

Câu trả lời này được tóm tắt từ Tải ảnh bitmap lớn một cách hiệu quả , giải thích cách sử dụng inSampleSize để tải phiên bản bitmap thu nhỏ.

Cụ thể , bitmap chia tỷ lệ trước giải thích chi tiết về các phương pháp khác nhau, cách kết hợp chúng và phương pháp nào hiệu quả nhất.

Có ba cách chính để thay đổi kích thước bitmap trên Android có các thuộc tính bộ nhớ khác nhau:

API createScaledBitmap

API này sẽ lấy một bitmap hiện có và tạo một bitmap MỚI với các kích thước chính xác mà bạn đã chọn.

Mặt tích cực, bạn có thể nhận được chính xác kích thước hình ảnh mà bạn đang tìm kiếm (bất kể nó trông như thế nào). Nhưng nhược điểm là API này yêu cầu một bitmap hiện có để hoạt động . Có nghĩa là hình ảnh sẽ phải được tải, giải mã và tạo bitmap trước khi có thể tạo một phiên bản mới, nhỏ hơn. Điều này là lý tưởng về mặt nhận được kích thước chính xác của bạn, nhưng khủng khiếp về mặt bộ nhớ bổ sung. Do đó, đây là một công cụ phá vỡ thỏa thuận đối với hầu hết các nhà phát triển ứng dụng, những người có xu hướng quan tâm đến trí nhớ

cờ inSampleSize

BitmapFactory.Optionscó một thuộc tính được lưu ý là inSampleSizesẽ thay đổi kích thước hình ảnh của bạn trong khi giải mã nó, để tránh phải giải mã thành một bitmap tạm thời. Giá trị số nguyên này được sử dụng ở đây sẽ tải một hình ảnh ở kích thước giảm 1 / x. Ví dụ: đặt inSampleSizethành 2 trả về một hình ảnh có kích thước bằng một nửa và Đặt thành 4 trả về hình ảnh có kích thước 1/4. Về cơ bản, kích thước hình ảnh sẽ luôn nhỏ hơn kích thước nguồn của bạn một số lũy thừa.

Từ góc độ bộ nhớ, sử dụng inSampleSizelà một hoạt động thực sự nhanh chóng. Một cách hiệu quả, nó sẽ chỉ giải mã mọi pixel thứ X của hình ảnh thành ảnh bitmap kết quả của bạn. Tuy nhiên, có hai vấn đề chính inSampleSize:

  • Nó không cung cấp cho bạn độ phân giải chính xác . Nó chỉ làm giảm kích thước bitmap của bạn bằng một số sức mạnh của 2.

  • Nó không tạo ra chất lượng tốt nhất thay đổi kích thước . Hầu hết các bộ lọc thay đổi kích thước tạo ra hình ảnh đẹp bằng cách đọc các khối pixel, sau đó cân chúng để tạo ra pixel được thay đổi kích thước được đề cập. inSampleSizetránh tất cả những điều này bằng cách chỉ đọc mỗi vài pixel. Kết quả là khá hiệu quả và bộ nhớ thấp, nhưng chất lượng bị ảnh hưởng.

Nếu bạn chỉ giải quyết việc thu nhỏ hình ảnh của mình theo một số kích thước pow2 và việc lọc không phải là vấn đề, thì bạn không thể tìm thấy phương pháp hiệu quả về bộ nhớ (hoặc hiệu suất hiệu quả) hơn inSampleSize.

cờ inScaled, inDensity, inTargetDensity

Nếu bạn cần phải mở rộng quy mô một hình ảnh đến một kích thước mà không bằng một sức mạnh của hai, sau đó bạn sẽ cần inScaled, inDensityinTargetDensitycờ của BitmapOptions. Khi inScaledcờ đã được đặt, hệ thống sẽ lấy giá trị tỷ lệ để áp dụng cho bitmap của bạn bằng cách chia cho inTargetDensitycác inDensitygiá trị.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

Sử dụng phương pháp này sẽ thay đổi kích thước hình ảnh của bạn và cũng áp dụng 'bộ lọc thay đổi kích thước' cho nó, nghĩa là, kết quả cuối cùng sẽ đẹp hơn vì một số phép toán bổ sung đã được tính đến trong bước thay đổi kích thước. Nhưng được cảnh báo: bước lọc bổ sung đó, tốn thêm thời gian xử lý và có thể nhanh chóng thêm vào các hình ảnh lớn, dẫn đến việc thay đổi kích thước chậm và phân bổ thêm bộ nhớ cho chính bộ lọc.

Nói chung không phải là ý kiến ​​hay khi áp dụng kỹ thuật này cho một hình ảnh lớn hơn đáng kể so với kích thước mong muốn của bạn, do chi phí lọc bổ sung.

Kết hợp ma thuật

Từ góc độ bộ nhớ và hiệu suất, bạn có thể kết hợp các tùy chọn này để có kết quả tốt nhất. (thiết lập inSampleSize, inScaled, inDensityinTargetDensitycờ)

inSampleSizetrước tiên sẽ được áp dụng cho hình ảnh, đưa nó lên kích thước LỚN hơn gấp hai lần tiếp theo so với kích thước mục tiêu của bạn. Sau đó, inDensity& inTargetDensityđược sử dụng để chia tỷ lệ kết quả theo kích thước chính xác mà bạn muốn, áp dụng thao tác lọc để làm sạch hình ảnh.

Kết hợp hai thao tác này là một thao tác nhanh hơn nhiều, vì inSampleSizebước này sẽ giảm số lượng pixel mà bước dựa trên Mật độ kết quả sẽ cần áp dụng bộ lọc thay đổi kích thước.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

Nếu bạn cần điều chỉnh một hình ảnh với kích thước cụ thể một số bộ lọc đẹp hơn, thì kỹ thuật này là cầu nối tốt nhất để có được kích thước phù hợp, nhưng được thực hiện trong một thao tác nhanh, ít bộ nhớ.

Nhận kích thước hình ảnh

Nhận kích thước hình ảnh mà không cần giải mã toàn bộ hình ảnh Để thay đổi kích thước ảnh bitmap của bạn, bạn sẽ cần biết các kích thước đến. Bạn có thể sử dụng inJustDecodeBoundscờ để giúp bạn nhận được kích thước của hình ảnh, bạn cần thực sự giải mã dữ liệu pixel.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

Bạn có thể sử dụng cờ này để giải mã kích thước trước tiên, sau đó tính toán các giá trị thích hợp để chia tỷ lệ theo độ phân giải mục tiêu của bạn.


1
thật tuyệt nếu bạn có thể cho chúng tôi biết dstWidth là gì?
k0sh

@ k0sh dstWIdth là chiều rộng của ImageView cho nơi nó sẽ tức destination widthhoặc dstWidth cho ngắn
tyczj

@tyczj cảm ơn vì câu trả lời, tôi biết nó là gì, nhưng có một số người có thể không biết và vì Colt đã thực sự trả lời câu hỏi này, có lẽ anh ấy có thể giải thích nó để mọi người không nhầm lẫn.
k0sh

Nice đăng ... CHƯA BIẾT VỀ THƯƠNG inScaled, inDensity, cờ inTargetDensity trước đây ...
maveroid

Tôi đã xem loạt mô hình hiệu suất android và tôi đã học được rất nhiều điều!
Anis

13

Dù hay (và chính xác) như câu trả lời này, nó cũng rất phức tạp. Thay vì phát minh lại bánh xe, hãy xem xét các thư viện như Glide , Picasso , UIL , Ion hoặc bất kỳ nào khác triển khai logic phức tạp và dễ mắc lỗi này cho bạn.

Bản thân Colt thậm chí còn khuyên bạn nên xem qua Glide và Picasso trong Video về các mẫu hiệu suất Bitmaps mở rộng quy mô trước .

Bằng cách sử dụng các thư viện, bạn có thể nhận được từng chút hiệu quả được đề cập trong câu trả lời của Colt, nhưng với các API đơn giản hơn rất nhiều, hoạt động nhất quán trên mọi phiên bản Android.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.