Last active
March 30, 2024 23:48
-
-
Save karnadii/a9f2fdac2186cc2ea713201a7efc2ca2 to your computer and use it in GitHub Desktop.
prefetch image size
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:typed_data'; | |
import 'package:http/http.dart' as http; | |
enum ImageFormat { jpg, gif, png, webp } | |
class Size { | |
final int width; | |
final int height; | |
Size(this.width, this.height); | |
} | |
class ImageInfo { | |
final String url; | |
final ImageFormat? format; | |
final Size? size; | |
ImageInfo(this.url, this.format, this.size); | |
} | |
class ImageSizeFetcher { | |
Future<List<ImageInfo>> fetchImageSizes(List<String> imageUrls) async { | |
final List<ImageInfo> imageInfoList = []; | |
for (String url in imageUrls) { | |
final client = http.Client(); | |
final request = http.Request('GET', Uri.parse(url)); | |
final response = await client.send(request); | |
final completer = Completer<ImageInfo>(); | |
var bytesRead = 0; | |
var buffer = BytesBuilder(); | |
// Determine the image format based on the URL extension | |
final imageFormat = _getImageFormatFromUrl(url); | |
late StreamSubscription subscription; | |
subscription = response.stream.listen( | |
(List<int> data) { | |
buffer.add(data); | |
bytesRead += data.length; | |
if (imageFormat != null) { | |
// Extract the image size based on the format | |
final size = _extractImageSize(buffer.toBytes(), imageFormat); | |
if (size != null) { | |
completer.complete(ImageInfo(url, imageFormat, size)); | |
subscription.cancel(); | |
client.close(); // Cancel the subscription to stop receiving more data | |
} | |
} else { | |
// If no format was determined from the URL, check the header | |
final headerFormat = _getImageFormatFromHeader(buffer.toBytes()); | |
if (headerFormat != null) { | |
final size = _extractImageSize(buffer.toBytes(), headerFormat); | |
if (size != null) { | |
completer.complete(ImageInfo(url, headerFormat, size)); | |
subscription.cancel(); | |
client.close(); // Cancel the subscription to stop receiving more data | |
} | |
} | |
} | |
}, | |
onDone: () { | |
if (!completer.isCompleted) { | |
completer.complete(ImageInfo(url, null, null)); | |
} | |
}, | |
onError: (error) { | |
completer.completeError(error); | |
}, | |
cancelOnError: true, | |
); | |
final imageInfo = await completer.future; | |
imageInfoList.add(imageInfo); | |
} | |
return imageInfoList; | |
} | |
ImageFormat? _getImageFormatFromUrl(String url) { | |
final uri = Uri.parse(url); | |
final extension = uri.pathSegments.last.split('.').last.toLowerCase(); | |
switch (extension) { | |
case 'jpg': | |
case 'jpeg': | |
return ImageFormat.jpg; | |
case 'gif': | |
return ImageFormat.gif; | |
case 'png': | |
return ImageFormat.png; | |
case 'webp': | |
return ImageFormat.webp; | |
default: | |
return null; | |
} | |
} | |
ImageFormat? _getImageFormatFromHeader(Uint8List bytes) { | |
if (bytes.length >= 2) { | |
if (bytes[0] == 0xFF && bytes[1] == 0xD8) { | |
return ImageFormat.jpg; | |
} else if (bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46) { | |
return ImageFormat.gif; | |
} else if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) { | |
return ImageFormat.png; | |
} else if (bytes[0] == 0x52 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x46) { | |
return ImageFormat.webp; | |
} | |
} | |
return null; | |
} | |
Size? _extractImageSize(Uint8List bytes, ImageFormat format) { | |
switch (format) { | |
case ImageFormat.jpg: | |
int i = 0; | |
// check for valid JPEG image | |
// http://www.fastgraph.com/help/jpeg_header_format.html | |
if (bytes[i] != 0xFF || bytes[i + 1] != 0xD8 || bytes[i + 2] != 0xFF) { | |
throw Exception('Not a valid JPEG image'); // Not a valid SOI header | |
} | |
i += 4; | |
// Check for valid JPEG header (null terminated JFIF) | |
// if (bytes[i + 2] != 0x4A || | |
// bytes[i + 3] != 0x46 || | |
// bytes[i + 4] != 0x49 || | |
// bytes[i + 5] != 0x46 || | |
// bytes[i + 6] != 0x00) { | |
// throw Exception('Not a valid JFIF image'); // Not a valid JFIF string | |
// } | |
// Retrieve the block length of the first block since the | |
// first block will not contain the size of file | |
int blockLength = bytes[i] * 256 + bytes[i + 1]; | |
do { | |
i += blockLength; // Increase the file index to get to the next block | |
if (i >= bytes.length) { | |
// Check to protect against segmentation faults | |
return null; | |
} | |
if (bytes[i] != 0xFF) { | |
// Check that we are truly at the start of another block | |
return null; | |
} | |
if (bytes[i + 1] >= 0xC0 && bytes[i + 1] <= 0xC3) { | |
// if marker type is SOF0, SOF1, SOF2 | |
// "Start of frame" marker which contains the file size | |
int height = bytes[i + 5] << 8 | bytes[i + 6]; | |
int width = bytes[i + 7] << 8 | bytes[i + 8]; | |
final size = Size(width, height); | |
return size; | |
} else { | |
// Skip the block marker | |
i += 2; | |
blockLength = bytes[i] * 256 + bytes[i + 1]; // Go to the next block | |
} | |
} while (i < bytes.length); | |
return null; | |
case ImageFormat.gif: | |
// GIF header format: GIF89a<width><height> | |
if (bytes.length >= 10) { | |
final width = bytes[6] | (bytes[7] << 8); | |
final height = bytes[8] | (bytes[9] << 8); | |
return Size(width, height); | |
} | |
break; | |
case ImageFormat.png: | |
// PNG header format: \x89PNG\r\n\x1a\n<length><IHDR> | |
if (bytes.length >= 24) { | |
final width = (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19]; | |
final height = (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23]; | |
return Size(width, height); | |
} | |
break; | |
case ImageFormat.webp: | |
// WebP header format: RIFF<fileSize>WEBPVP8<width><height> | |
if (bytes.length >= 30) { | |
final width = (bytes[26] << 8) | bytes[27]; | |
final height = (bytes[28] << 8) | bytes[29]; | |
return Size(width, height); | |
} | |
break; | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment