概述

#

原始影像在 Web 上的渲染方式已得到更正,現在與其他平臺一致。這會影響到需要向 ui.ImageDescriptor.rawui.decodeImageFromPixels 提供不正確資料的舊版應用,這些應用會導致生成的影像上下顛倒且顏色不正確(其紅色和藍色通道被交換)。

背景

#

Flutter 內部使用的“畫素流”始終定義為相同的格式:對於每個畫素,四個 8 位通道按 format 引數定義的順序打包,然後按從左到右的順序分組,然後按從上到下的順序分組。

然而,Flutter for Web,或者更具體地說,HTML 渲染器,由於對 BMP 格式規範的理解錯誤,曾經以錯誤的方式實現它。結果是,如果應用或庫使用 ui.ImageDescriptor.rawui.decodeImageFromPixels,它必須從下到上提供畫素並交換其紅色和藍色通道(例如,使用 ui.PixelFormat.rgba8888 格式時,資料的前 4 個位元組被視為第一個畫素的藍色、綠色、紅色和 alpha 通道)。

此 bug 已透過 engine#29593 修復,但應用和庫必須更正其資料生成方式。

變更說明

#

ui.ImageDescriptor.rawui.decodeImageFromPixelspixels 引數現在使用 format 所描述的正確畫素順序,並且源自左上角。

直接呼叫這兩個函式渲染的影像 舊程式碼直接呼叫這些函式可能會發現其影像上下顛倒且顏色不正確。

遷移指南

#

如果應用使用最新版本的 Flutter 並遇到這種情況,最直接的解決方案是手動翻轉影像,並使用替代的畫素格式。然而,這不太可能是最最佳化的解決方案,因為此類畫素資料通常是從其他來源構建的,可以在構建過程中進行翻轉。

遷移前的程式碼

dart
import 'dart:typed_data';
import 'dart:ui' as ui;

// Parse `image` as a displayable image.
//
// Each byte in `image` is a pixel channel, in the order of blue, green, red,
// and alpha, starting from the bottom left corner and going row first.
Future<ui.Image> parseMyImage(Uint8List image, int width, int height) async {
  final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw(
    await ui.ImmutableBuffer.fromUint8List(image),
    width: width,
    height: height,
    pixelFormat: ui.PixelFormat.rgba8888,
  );
  return (await (await descriptor.instantiateCodec()).getNextFrame()).image;
}

遷移後的程式碼

dart
import 'dart:typed_data';
import 'dart:ui' as ui;

Uint8List verticallyFlipImage(Uint8List sourceBytes, int width, int height) {
  final Uint32List source = Uint32List.sublistView(ByteData.sublistView(sourceBytes));
  final Uint32List result = Uint32List(source.length);
  int sourceOffset = 0;
  int resultOffset = 0;
  for (final int row = height - 1; row >= 0; row -= 1) {
    sourceOffset = width * row;
    for (final int col = 0; col < width; col += 1) {
      result[resultOffset] = source[sourceOffset];
      resultOffset += 1;
      sourceOffset += 1;
    }
  }
  return Uint8List.sublistView(ByteData.sublistView(sourceBytes))
}

Future<ui.Image> parseMyImage(Uint8List image, int width, int height) async {
  final Uint8List correctedImage = verticallyFlipImage(image, width, height);
  final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw(
    await ui.ImmutableBuffer.fromUint8List(correctedImage),
    width: width,
    height: height,
    pixelFormat: ui.PixelFormat.rgba8888,
  );
  return (await (await descriptor.instantiateCodec()).getNextFrame()).image;
}

一個更棘手的情況是,當你編寫一個庫,並希望該庫能夠在最新的 Flutter 和補丁版本之前的一個版本上都能工作。在這種情況下,你可以透過先解碼單個畫素來決定行為是否已更改。

遷移後的程式碼

dart
Uint8List verticallyFlipImage(Uint8List sourceBytes, int width, int height) {
  // Same as the example above.
}

late Future<bool> imageRawUsesCorrectBehavior = (() async {
  final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw(
    await ui.ImmutableBuffer.fromUint8List(Uint8List.fromList(<int>[0xED, 0, 0, 0xFF])),
    width: 1, height: 1, pixelFormat: ui.PixelFormat.rgba8888);
  final ui.Image image = (await (await descriptor.instantiateCodec()).getNextFrame()).image;
  final Uint8List resultPixels = Uint8List.sublistView(
    (await image.toByteData(format: ui.ImageByteFormat.rawStraightRgba))!);
  return resultPixels[0] == 0xED;
})();

Future<ui.Image> parseMyImage(Uint8List image, int width, int height) async {
  final Uint8List correctedImage = (await imageRawUsesCorrectBehavior) ?
    verticallyFlipImage(image, width, height) : image;
  final ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw(
    await ui.ImmutableBuffer.fromUint8List(correctedImage), // Use the corrected image
    width: width,
    height: height,
    pixelFormat: ui.PixelFormat.bgra8888, // Use the alternate format
  );
  return (await (await descriptor.instantiateCodec()).getNextFrame()).image;
}

時間線

#

已登入到版本:2.9.0-0.0.pre
穩定版本: 2.10

參考資料

#

API 文件

相關問題

相關 PR