Skip to content

Commit

Permalink
enumify ImageType and ImageFileSignature (flutter#54131)
Browse files Browse the repository at this point in the history
Use enhanced enums to clarify the image detection code.

Part of a refactor towards flutter/flutter#151911

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
harryterkelsen authored Jul 30, 2024
1 parent e2ece7e commit 537df75
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 122 deletions.
188 changes: 68 additions & 120 deletions lib/web_ui/lib/src/engine/image_format_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ImageType? detectImageType(Uint8List data) {
}

formatLoop:
for (final ImageFileFormat format in ImageFileFormat.values) {
for (final ImageFileSignature format in ImageFileSignature.values) {
if (data.length < format.header.length) {
continue;
}
Expand All @@ -34,62 +34,16 @@ ImageType? detectImageType(Uint8List data) {
}
}

final ImageFileType fileType = format.fileType;
// TODO(harryterkelsen): If the image is animated, we use Skia to decode.
// This is currently too conservative, assuming all GIF and WEBP images are
// animated. We should detect if they are actually animated by reading the
// image headers, https://github.com/flutter/flutter/issues/151911.
return ImageType(fileType,
isAnimated:
fileType == ImageFileType.gif || fileType == ImageFileType.webp);
return format.imageType;
}

if (isAvif(data)) {
return const ImageType(ImageFileType.avif);
return ImageType.avif;
}

return null;
}

/// The file format of the image, and whether or not it is animated.
class ImageType {
const ImageType(this.fileType, {this.isAnimated = false});

final ImageFileType fileType;
final bool isAnimated;

/// The MIME type for this image (e.g 'image/jpeg').
String get mimeType {
String fileString;
switch (fileType) {
case ImageFileType.png:
fileString = 'png';
case ImageFileType.gif:
fileString = 'gif';
case ImageFileType.jpeg:
fileString = 'jpeg';
case ImageFileType.webp:
fileString = 'webp';
case ImageFileType.bmp:
fileString = 'bmp';
case ImageFileType.avif:
fileString = 'avif';
}
return 'image/$fileString';
}

@override
bool operator ==(Object other) {
if (other is! ImageType) {
return false;
}
return other.fileType == fileType && other.isAnimated == isAnimated;
}

@override
int get hashCode => Object.hash(fileType, isAnimated);
}

/// The supported image file formats in Flutter Web.
enum ImageFileType {
png,
Expand All @@ -100,9 +54,69 @@ enum ImageFileType {
avif,
}

/// Represents an image file format, such as PNG or JPEG.
class ImageFileFormat {
const ImageFileFormat(this.header, this.fileType);
/// The file format of the image, and whether or not it is animated.
enum ImageType {
// TODO(harryterkelsen): If the image is animated, we use Skia to decode.
// This is currently too conservative, assuming all GIF and WEBP images are
// animated. We should detect if they are actually animated by reading the
// image headers, https://github.com/flutter/flutter/issues/151911.
png(ImageFileType.png, isAnimated: false),
gif(ImageFileType.gif, isAnimated: true),
jpeg(ImageFileType.jpeg, isAnimated: false),
webp(ImageFileType.webp, isAnimated: true),
bmp(ImageFileType.bmp, isAnimated: false),
avif(ImageFileType.avif, isAnimated: false);

const ImageType(this.fileType, {required this.isAnimated});

final ImageFileType fileType;
final bool isAnimated;

/// The MIME type for this image (e.g 'image/jpeg').
String get mimeType => 'image/${fileType.name}';
}

/// The signature bytes in an image file that identify the format.
enum ImageFileSignature {
png(
<int?>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
ImageType.png,
),
gif87a(
<int?>[0x47, 0x49, 0x46, 0x38, 0x37, 0x61],
ImageType.gif,
),
gif89a(
<int?>[0x47, 0x49, 0x46, 0x38, 0x39, 0x61],
ImageType.gif,
),
jpeg(
<int?>[0xFF, 0xD8, 0xFF],
ImageType.jpeg,
),
webp(
<int?>[
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50
],
ImageType.webp,
),
bmp(
<int?>[0x42, 0x4D],
ImageType.bmp,
);

const ImageFileSignature(this.header, this.imageType);

/// First few bytes in the file that uniquely identify the image file format.
///
Expand All @@ -113,74 +127,8 @@ class ImageFileFormat {
/// (animated cursor), .cda, and other formats that start with "RIFF".
final List<int?> header;

/// The value that's passed as [_ImageDecoderOptions.type].
///
/// The server typically also uses this value as the "Content-Type" header,
/// but servers are not required to correctly detect the type. This value
/// is also known as MIME type.
final ImageFileType fileType;

/// All image file formats known to the Flutter Web engine.
///
/// This list may need to be changed as browsers adopt new formats, and drop
/// support for obsolete ones.
///
/// This list is checked linearly from top to bottom when detecting an image
/// type. It should therefore contain the most popular file formats at the
/// top, and less popular towards the bottom.
static const List<ImageFileFormat> values = <ImageFileFormat>[
// ICO is not supported in Chrome. It is deemed too simple and too specific. See also:
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/webcodecs/image_decoder_external.cc;l=38;drc=fd8802b593110ea18a97ef044f8a40dd24a622ec

// PNG
ImageFileFormat(
<int?>[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
ImageFileType.png,
),

// GIF87a
ImageFileFormat(
<int?>[0x47, 0x49, 0x46, 0x38, 0x37, 0x61],
ImageFileType.gif,
),

// GIF89a
ImageFileFormat(
<int?>[0x47, 0x49, 0x46, 0x38, 0x39, 0x61],
ImageFileType.gif,
),

// JPEG
ImageFileFormat(
<int?>[0xFF, 0xD8, 0xFF],
ImageFileType.jpeg,
),

// WebP
ImageFileFormat(
<int?>[
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50
],
ImageFileType.webp,
),

// BMP
ImageFileFormat(
<int?>[0x42, 0x4D],
ImageFileType.bmp,
),
];
/// The type of image that has the signature bytes in [header] in its header.
final ImageType imageType;
}

/// Function signature of [debugImageTypeDetector], which is the same as the
Expand Down
3 changes: 1 addition & 2 deletions lib/web_ui/test/canvaskit/image_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,7 @@ void _testCkAnimatedImage() {

// The precise PNG encoding is browser-specific, but we can check the file
// signature.
expect(detectImageType(png!.buffer.asUint8List()),
const ImageType(ImageFileType.png));
expect(detectImageType(png!.buffer.asUint8List()), ImageType.png);
});

test('CkAnimatedImage toByteData(RGBA)', () async {
Expand Down

0 comments on commit 537df75

Please sign in to comment.