Làm thế nào để sử dụng file SVG tốt hơn trong các ứng dụng Flutter?

Làm thế nào để sử dụng file SVG tốt hơn trong các ứng dụng Flutter?

Là định dạng tiêu chuẩn cho đồ họa vector, Scalable Vector Graphics (SVG) có những lợi thế về độ phân giải hình ảnh mà bitmap không thể so sánh được. Vì vậy, phải chăng SVG luôn là sự lựa chọn tốt nhất? Đương nhiên là không hoàn toàn vậy. Bài viết này sẽ khám phá hiệu suất của SVG trong các ứng dụng Flutter. Ngoài ra, nó sẽ chia sẻ các nghiên cứu thực tiễn được thực hiện bởi technology team nòng cốt của UC Browser cho việc sử dụng SVG tốt hơn trong các ứng dụng Flutter.

Ví dụ
Trong thế giới điện toán (computing world), có nhiều phương pháp tối ưu hóa không gian liên quan đến việc giảm tiêu thụ computing power. Ví dụ: một hình ảnh 4K với nhiều màu sắc và hình dạng có thể được nén ở kích thước 5 kB (trên thực tế, nếu màn hình đủ lớn, bạn thậm chí có thể nhìn thấy hình ảnh 8K). Nếu hình ảnh 5 kB được lưu trữ ở định dạng Portable Network Graphic (PNG), nó sẽ trông nhỏ hơn nhiều và sẽ bị chia thành nhiều pixel hơn nếu phóng to, nhưng đồng thời cũng sẽ xử lý nhanh hơn nhiều.

Hai hình ảnh sẽ khác nhau đáng kể về hiệu suất độ phân giải.

Để đạt được độ phân giải tương tự, các hệ điều hành thường hỗ trợ các ứng dụng bằng cách phân loại hình ảnh có nhiều độ phân giải thành các resource package giao diện người dùng (UI).

Resource package cho preceding icon có kích thước 120 KB, với phiên bản lớn nhất yêu cầu dung lượng bộ nhớ 4 MB để chạy.

Vì vậy, phải chăng SVG luôn là sự lựa chọn tốt nhất?

Không nhé. Trước khi bạn phân tích SVG support trong Flutter như thế nào, bạn có thể thấy việc Flutter không có support hiển thị ảnh SVG là một khiếm khuyết lớn.

Tuy nhiên, sau khi bạn phân tích dữ liệu rasterization cost, bạn sẽ hiểu những lo ngại về các vấn đề trong hệ thống có thể gây ra do sử dụng định dạng SVG một cách mù quáng.

Ví dụ: trên điện thoại di động có CPU Snapdragon 626, phải mất 34 mili giây để Flutter xử lý hình ảnh SVG trước đó trong vùng 64¡Á64. Do đó, một hình ảnh SVG hoàn toàn có thể ngăn các ứng dụng chạy trơn tru ở tốc độ 60 khung hình / giây (fps). Trong một bài kiểm tra, quá trình rasterization này trên iPhone X mất 8 mili giây để hoàn thành và chỉ có hai hình ảnh SVG có thể được hiển thị trơn tru.

Ngoài ra, nhu cầu áp dụng SVG hoặc đồ họa vector phát sinh sau khi xu hướng thiết kế UI phẳng xuất hiện. Khi thiết kế giao diện người dùng đa dạng là xu hướng thống trị, ngoài tốc độ rasterization, đồ họa vector có thể gây ra những khiếm khuyết không thể chấp nhận được trong các icon thực tế.

Ví dụ: sau khi chúng ta thực hiện vectơ hóa bằng cách sử dụng một thuật toán theo dõi tích cực hơn, như hiển thị ở bên phải trong hình sau, hình ảnh con chó trông giống như một tác phẩm nghệ thuật kỹ thuật số. Ngoài ra, phiên bản hình ảnh mới đã ngốn dung lượng lưu trữ hơn nhiều so với định dạng PNG.

May mắn thay, khi chúng ta sử dụng đồ họa vector để tiến hành các dự án, chúng ta chủ yếu sử dụng các thiết kế đơn giản để ngăn chặn vấn đề trước đó. Do đó, nếu chúng ta có thể tránh được những cạm bẫy, SVG vẫn có thể cung cấp hiệu suất tuyệt vời trong nhiều tình huống.

Ứng dụng hiện tại

Phiên bản chính của Flutter không hỗ trợ SVG

Có một thư mục SVG trong code của Skia, là một thành phần cơ bản của Flutter. Tuy nhiên, Skia chỉ có thể tuần tự hóa hình ảnh thành tệp SVG. Do đó, bạn không thể decode hoặc render hình ảnh SVG với Skia.

Hơn nữa, kế hoạch phát triển hiện tại cho framework của Flutter không bao gồm mục đích hỗ trợ SVG:

Design a new vector file format · Issue #1831 · flutter/flutter
Latest update: #1831 (comment) From earlier discussion, some important considerations are: We don't want full SVG support. There is too much in the spec that is expensive, heavyweight, and/or d...

Các hệ điều hành không có hỗ trợ SVG

Điều này có nghĩa các hệ điều hành lớn như Android và iOS không hỗ trợ SVG theo mặc định.

. https://stackoverflow.com/questions/34990236/how-to-use-svg-image-in-imageview
. https://stackoverflow.com/questions/35691839/how-to-display-svg-image-using-swift


Có một sự nhất trí rằng hỗ trợ SVG đầy đủ tính năng không chỉ đòi hỏi khối lượng công việc lớn mà còn có thể gây ra các vấn đề về hiệu suất. Những khiếm khuyết này được đề cập một cách mơ hồ bởi các hệ điều hành.

Không giống như SVG, vector fonts sẽ được hỗ trợ

Trong các cuộc tham vấn trước đây về SVG, tất cả các giải pháp được đề xuất đều bao gồm việc sử dụng vector fonts. Vector fonts có các tính năng sau:

. Hệ điều hành mainstream cung cấp native support cho chúng.
. Về cơ bản chúng là monochrome.
. Chúng độc lập trên các tệp Extensible Markup Language (XML) có thể mở rộng.
. Do monochrome output, nhiều yếu tố không thể kiểm soát có tác động đến hiệu suất, chẳng hạn như layer rendering và overlays bị loại trừ.
. Chúng hỗ trợ quản lý bộ nhớ đệm bitmap trong hệ thống. Chúng ta có thể khám phá thêm điều này khi build các công cụ dành cho các developer trong tương lai.
. Mặc dù có nhiều công việc đã được thực hiện khi khai phá SVG, chúng ta phải thừa nhận rằng output của vector fonts cho đến nay là một giải pháp thiết thực và hiệu quả.

Cải thiện ứng dụng SVG bằng cách kết hợp với các công cụ và quy trình

Là một định dạng tiêu chuẩn mạnh mẽ dành cho đồ họa vector, SVG phù hợp với một số ứng dụng nhất định, chẳng hạn như các biểu tượng đầy màu sắc, cập nhật cuộn thuận tiện và một loạt các công cụ sản xuất hỗ trợ định dạng này.

Make SVG Great Again!
Cả hệ điều hành và runtime environment đều đã loại trừ SVG. Tuy nhiên, với flutter_svg package, Flutter có khả năng render và decode SVG một cách đơn giản và nhanh chóng. Điều này cho thấy khả năng mở rộng tuyệt vời của Flutter và Dart.

flutter_svg package rất dễ sử dụng. Nó cung cấp các giao diện tương tự như giao diện image_provider trong framework của Flutter. Các đoạn code sau được sử dụng để hiển thị hình ảnh SVG tương ứng từ asset và network.

SvgPicture.asset(
    'assets/adsmall.svg',
    placeholderBuilder: (BuildContext context) => Container(
        child: const CircularProgressIndicator()),
),

SvgPicture.network(
    'https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/new-camera.svg',
    placeholderBuilder: (BuildContext context) => Container(
        child: const CircularProgressIndicator()),
),

Sử dụng các công cụ để tránh pitfall
Chúng ta không được bỏ qua các vấn đề về hiệu suất tiềm ẩn của SVG.

Nhóm công nghệ cốt lõi của UC Browser đã phát triển một resource panel để giúp đối phó với các vấn đề tiềm ẩn. Với panel này, bạn có thể dễ dàng kết nối với các ứng dụng Flutter và xem phân bổ bộ nhớ trong thời gian thực. Ngoài ra, bạn có thể xem trước hình ảnh SVG và lấy chi phí rasterization của chúng bằng cách sử dụng resource panel.

Bằng cách record và so sánh chi phí rasterization của tệp SVG trên thiết bị di động thực, bạn có thể dễ dàng xác định tệp SVG có vấn đề tiềm ẩn. Do đó, bạn có thể có sự sắp xếp phù hợp cho các ứng dụng SVG.

Bằng cách so sánh chi phí rasterization thực tế, chúng ta nhận thấy rằng thời gian tiêu tốn của một icon có thiết kế đơn giản là 16,66 ms. Điều này có thể chấp nhận được trên các thiết bị di động có CPU Snapdragon 626.

flutter_svg hoạt động như thế nào?

flutter_svg
Là một Dart package, flutter_svg cung cấp khả năng decode các tệp SVG từ network, asset và bộ nhớ.

Các tệp được decode không phải là hình ảnh bitmap như ui.Image class. Do đó, thay vì làm việc với image cache, flutter_svg package đã triển khai picture cache của riêng nó. Picture cache lưu trữ một class có tên ui.Picture, thực chất là SkPicture wrapper của Skia Graphics Engine. Class này record các lệnh render SVG cụ thể ở chế độ nhị phân.

ui.Picture class không tiêu tốn nhiều bộ nhớ. Nó được lưu vào bộ nhớ đệm để tránh phân tích cú pháp lặp lại các tệp XML.

Ví dụ: đoạn code sau xác định giao diện xây dựng của SvgPicture.asset.

 SvgPicture.asset(
    String assetName, {
    Key key,
    this.matchTextDirection = false,
    AssetBundle bundle,
    String package,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    Color color,
    BlendMode colorBlendMode = BlendMode.srcIn,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
  })  : pictureProvider = ExactAssetPicture(
            allowDrawingOutsideViewBox == true
                ? svgStringDecoderOutsideViewBox
                : svgStringDecoder,
            assetName,
            bundle: bundle,
            package: package,
            colorFilter: _getColorFilter(color, colorBlendMode)),
        super(key: key);

Hình ảnh của SvgPicture được cập nhật bằng stream notification từ picture provider.

void _resolveImage() {
    final PictureStream newStream = widget.pictureProvider
        .resolve(createLocalPictureConfiguration(context));
    assert(newStream ! = null);
    _updateSourceStream(newStream);
  }

Stream từ picture provider được điền bằng ui.Picture bởi một bộ đệm hoàn chỉnh của picture cache.

// in PictureProvider<T>.resolve
      stream.setCompleter(
        _cache.putIfAbsent(
          key,
          () => load(key, onError: onError),
        ),
      );

Trong chế độ Debug and Profile, bằng cách thêm một số code nhất định, bạn có thể truy vấn tất cả các ảnh hiện có của SvgPicture trong picture cache bằng các công cụ dành cho developer.

Quản lý thời lượng Rasterization
Giao diện bắt đầu quá trình rasterization bằng cách sử dụng phương thức ui.Picture.toImage. Rasterizer thread sẽ chịu trách nhiệm về thời gian.

Thông tin thêm

Android VectorDrawable
Android đã cung cấp VectorDrawable, là phiên bản đơn giản hóa của SVG. Mặc dù định dạng và thuộc tính của giải pháp này không hoàn toàn tương thích với SVG, nhưng chỉ cần một công cụ chuyển đổi có sẵn thì sẽ có thể  giải quyết vấn đề này. Các tài liệu cho thấy những lo ngại về việc mất hiệu suất có thể gây ra bởi các hình ảnh SVG phức tạp. Tham khảo: https://developer.android.com/studio/write/vector-asset-studio

Tối ưu hóa bộ đệm bitmap cho các tệp SVG riêng lẻ
Phiên bản hiện tại của Flutter thực hiện quá trình rasterization một lần và xuất ra mọi khung hình. Chế độ này khác với trình tổng hợp Chromium (cc), tạo ra các ảnh bitmap cho từng khu vực và sau đó hợp nhất các hình ảnh này. Trong bước đầu ra của rasterization trong Flutter, nếu bạn đánh dấu các ảnh bitmap của SVG và lưu chúng vào bộ nhớ cache, bạn có thể tăng giá trị fps. Tuy nhiên, phương pháp này sẽ dẫn đến việc sử dụng bộ nhớ nhiều hơn.

Hiện tại thì sẽ khá bất tiện nếu thực hiện chức năng này chỉ với Dart, vì RenderPicture không thể ước tính được độ phân giải rasterization cụ thể trong dart.ui thread.

Tổng kết: Nên sử dụng file SVG cho những trường hợp nào?

File SVG thích hợp để sử dụng trong Flutter khi:

  1. Cần hình ảnh có độ phóng đại mà không bị mất chất lượng: File SVG có thể được phóng to hoặc thu nhỏ mà vẫn giữ độ sắc nét.
  2. Muốn tạo hình ảnh động hoặc điều chỉnh hình ảnh trong thời gian chạy: File SVG có thể điều chỉnh hoặc tạo hình ảnh động trong thời gian chạy bằng cách sử dụng các thuộc tính hoạt cảnh hoặc Javascript.
  3. Cần hình dạng có thể tùy biến: File SVG cho phép tạo các hình dạng động hoặc tùy biến theo nhu cầu của người dùng.
  4. Sử dụng SVG cho các icon của Bottom Navigator Bar, dùng cho các biểu tượng và hình logo, hình ảnh động, các hình dạng tròn, elip, polygon, vv.

Bài viết được lược dịch từ Lingfeng.