Cách tính size (dung lượng) của chuỗi base64 trong JS

Code tính size cho bạn lười đọc (đọc hết bài sẽ hiểu cách mình tính size base64 như thế nào):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function calculateImageSize(base64String) {
let padding;

if(base64String.endsWith("==")) {
padding = 2;
}
else if (base64String.endsWith("=")) {
padding = 1;
}
else {
padding = 0;
}

const base64StringLength = base64String.length;

const bytes = (3 * base64StringLength / 4) - padding;

const kBytes = bytes / 1000;

return kBytes;
}

// cách dùng, gọi hàm trên và truyền vào base64
calculateImageSize('.... base64 image ...');

Khi làm việc với chức năng upload ảnh, thông thường sẽ có nhiều cách để làm. Cách mình thấy phổ biến nhất đó là upload file lên, sau đó lấy đường dẫn file, lưu đường dẫn này vào database. Ngoài cách đó ra còn kha khá idea thú zị khác nữa. Trong đó cách chuyển ảnh ra base64, lấy chuỗi base64 này lưu vào database cũng là cách vui nhộn không kém. :))

Nói vậy chứ mình không làm theo cách này, chỉ là hôm nay mình thấy có người làm vậy và phát sinh vài issue trong đó. Mình tranh thủ ngó nghiêng xem họ bị chi, nhân cơ hội ra tay thể hiện… à nhầm ra tay nghĩa hiệp support đồng đội. Trước tiên để mình kể lễ về sự việc đã.

Issue

Issue phát sinh từ việc không validate size của ảnh đó. Giữ nguyên ảnh gốc, encode nó sang base64, lấy base64 này gửi lên server và lưu xuống database (ảnh này nặng khoảng 15mb, encode ra base64 thì nặng nữa). Lúc này phát sinh hai vấn đề:

  1. Cái request lên server rất khủng khiếp, tốn time phát khiếp (vì encode base64 tại client và gửi chuỗi đi)
  2. Lúc cần hiển thị ảnh, response trả về base64, lúc này vì độ dài base64 dài quá, nó break luôn (hiển thị có phần bức ảnh hoặc ảnh bị lỗi) @@.

Giải quyết

Sau hồi hỏi thăm nhẹ nhàng bác google, anh ấy đã giải quyết xong. Dùng hàm hỗ trợ sẵn, vẽ lại ảnh đó lên canvas, set lại width, height, COMPRESS lại ảnh. Nói chung oke cả, chỗ ni chả có chi lăng tăng để mình nói nhiều cả. Bạn chỉ cần gg 5s ra cách liền.

Mấu chốt chỗ này chính là compress (nén) ảnh trước khi encode ra base64.

Cách tính size base64 images string

Lúc này tự dưng nảy sinh thêm một vấn đề mới, anh ấy muốn check xem base64 đó có dung lượng bao nhiêu kí (kb) để validate nó. Thực ra validate size bằng kiểu file thì bình thường quá nhỉ, nay validate size của base64 mới gê :v. Thế mới thể hiện đẳng cấp được. Nói vậy chớ mình thấy lạ, nhưng vì đề bài quá mới, nên thấy khá thú zị.

Validate file upload bình thường.

1
2
3
4
5
6
var input = document.getElementById('fileinput');
var file = input.files[0];

var imgKBytes = file.size / 1000;

// TODO validate...

Các bước tính toán

Chơi kiểu khó, thử tính size của base64 nhé:

  1. Theo như Base64 trên Wiki thì mỗi ký tự (chữ số,…) sẽ thể hiện 6 bits bộ nhớ.
  2. Một bit sẽ bằng 1/8 byte => 1 byte bằng 8 bits.
  3. Từ đó suy ra chỉ cần lấy độ dài của base64 nhân với 6 bits, sẽ ra tổng bit đã sử dụng. Lấy tổng bit đã sử dụng chia cho 8 sẽ tính được số bytes.
    Ví dụ sau khi convert ảnh ra base64 thì được length là 10. Lấy 10 * 6 = 60 bits, lấy 60 / 8 = 7.5, kết quả 7.5 bytes.
  4. Tới đây chưa xong đâu, khi tổng số bit không chia hết cho 6 thì nó sẽ padding vào = hoặc == để cho đủ bit, vì vậy để tính chính xác chúng ta phải trừ phần padding này ra. Ví dụ chuỗi này được padding thêm hai dấu bằng vào cuối, ta lấy 7.5 - 2.
  5. Vậy công thức hoàn chỉnh như sau:
1
(6 * lengthInCharacters / 8) - numberOfPaddingCharacters
  1. Để chuyển ra kilobyte thì cứ chia 1000 thoi :)).
1
((6 * lengthInCharacters / 8) - numberOfPaddingCharacters) / 1000

Để dễ nhìn và dễ tính toán, chúng ta sẽ thực hiện rút gọn phân số. Công thức sau khi rút gọn:

1
(3 * lengthInCharacters / 4) - numberOfPaddingCharacters

Ví dụ

Ví dụ đầu tiên độ dài của base64 là 15 ký tự, và cuối chuỗi base64 có 1 dấu =. Ráp vào công thức (3 * 15 / 4) - 1 = 10.25 bytes. Chia cho 1000 để ra số kilobyte ta sẽ được 0.01025 kb.

Viết hàm tính toán trong Js (ngôn ngữ khác tương tự nhé):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function calculateImageSize(base64String) {
let padding;

if(base64String.endsWith("==")) {
padding = 2;
}
else if (base64String.endsWith("=")) {
padding = 1;
}
else {
padding = 0;
}

const base64StringLength = base64String.length;

const bytes = (3 * base64StringLength / 4) - padding;

// chuyển sang hệ decimal là chia 1000 (đây là hệ mà các nhà sản xuất ổ cứng tính)
// hệ binary là chia cho 1024
const kBytes = bytes / 1000;

return kBytes;
}

Bonus

Thêm nữa có một cách tính dựa trên MINE của base64, con số cho ra gần chính xác:

1
bytes = (string_length(encoded_string) - 814) / 1.37

Lưu ý cách này chỉ có tác dụng khi bạn có thông tin header request lên server.

Chú thích:
1.37: Đây là con số mà các nhà khoa học máy tính, tính toán ra, nó chỉ ước lượng chứ không đúng tuyệt đối.
814: 814 bytes (phần headers, tổng byte của tất cả các fields header MIME header fields)

Quay lại ví dụ ở trên. Giả sử, mình đã trừ hết MINE header ra rồi. Lúc này base64 có length là 15, ráp công thứ này vào 15 / 1.37 = 10.9489051095 bytes. Gần giống với số trên đúng không.

Ngoài ra dựa vào công thức (3 * lengthInCharacters / 4) - numberOfPaddingCharacters, ta có thể suy ngược ra cách tính số lượng ký tự base64 dựa vào size của ảnh gốc mà không cần encode ảnh ra base64. Công thức như sau:

1
lengthInCharacters + numberOfPaddingCharacters = (4 / 3) * size ảnh

Ở ví dụ trên, mình đã ví dụ base64 có 15 ký tự ngốn khoảng 10.25 byte. Giờ ráp vào công thức ta tính được số ký tự nếu encode ra base64 là: (4 / 3) * 10.25 = 13.66 ký tự (chưa bao gồm padding). Vậy nếu thêm 1 hoặc 2 padding thì length rơi vào khoảng 15 ký tự. Thấy đúng với ví dụ ở trên chưa.

Tham khảo

 Comments
Comment plugin failed to load
Loading comment plugin