網頁上的原始圖像使用正確的原點和色彩
摘要
#網頁上原始圖像的渲染方式已修正,現在與其他平台一致。這會破壞必須將不正確的資料傳遞給 ui.ImageDescriptor.raw
或 ui.decodeImageFromPixels
的舊版應用程式,導致產生的圖像上下顛倒且色彩不正確(紅色和藍色通道被交換)。
背景
#Flutter 內部使用的「像素流」一直以來都被定義為相同的格式:對於每個像素,四個 8 位元的通道會按照 format
參數定義的順序封裝,然後從左到右分組為一行,接著由上到下分行。
然而,Flutter for Web,更具體地說是 HTML 渲染器,過去由於對 BMP 格式規範的錯誤理解而以錯誤的方式實作它。因此,如果應用程式或函式庫使用 ui.ImageDescriptor.raw
或 ui.decodeImageFromPixels
,就必須從下到上提供像素,並交換其紅色和藍色通道(例如,對於 ui.PixelFormat.rgba8888
格式,資料的前 4 個位元組被視為第一個像素的藍色、綠色、紅色和 alpha 通道)。
這個錯誤已透過 engine#29593 修正,但應用程式和函式庫必須修正其資料的產生方式。
變更說明
#ui.ImageDescriptor.raw
或 ui.decodeImageFromPixels
的 pixels
參數現在使用 format
描述的正確像素順序,並且原點位於左上角。
直接呼叫這兩個函式渲染的圖像。直接呼叫這些函式的舊程式碼可能會發現其圖像上下顛倒且色彩不正確。
移轉指南
#如果應用程式使用最新版本的 Flutter 並遇到這種情況,最直接的解決方案是手動翻轉圖像,並使用替代的像素格式。但是,這不太可能是最優化的解決方案,因為這種像素資料通常是從其他來源建構的,允許在建構過程中進行翻轉。
遷移前的程式碼
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;
}
遷移後的程式碼
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 和修補程式之前的版本上運作。在這種情況下,您可以透過讓它先解碼單個像素來判斷行為是否已變更。
遷移後的程式碼
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
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新於 2024-04-04。 檢視原始碼 或 回報問題。