dart_image_size_getter
dart_image_size_getter copied to clipboard
[Discussions] Unhandled Exception: Unsupported operation: The input is not supported.
Content
when select jpg image get error for some files. final size = ImageSizeGetter.getSize(FileInput(file));
why get error with jpg file that take from camera?
I face same issue, here is file.
I found it have a [0x20] in end of file. after [0xFF, 0xD9]. Is it possiable add error handle for this case ?
Use this. working with your image.
var info = ImageSizeData.fromBytes(file);
info.width
info.height
info.format
- image_info.dart :
import 'dart:io';
import 'dart:typed_data';
import 'package:collection/collection.dart';
/// Image formats supported by Flutter.
enum ImageFormat {
/// A Portable Network Graphics format image.
png,
/// A JPEG format image.
///
/// This library does not support JPEG 2000.
jpeg,
/// A WebP format image.
webp,
/// A Graphics Interchange Format image.
gif,
/// A Windows Bitmap format image.
bmp,
}
/// Provides details about image format information for raw compressed bytes
/// of an image.
abstract class ImageSizeData {
/// Allows subclasses to be const.
const ImageSizeData({
required this.format,
required this.width,
required this.height,
}) : assert(width >= 0),
assert(height >= 0);
/// Creates an appropriate [ImageSizeData] for the source `bytes`, if possible.
///
/// Only supports image formats supported by Flutter.
factory ImageSizeData.fromBytes(file) {
var bytes = file.readAsBytesSync();
ByteData byteData = bytes.buffer.asByteData();
if (bytes.isEmpty) {
throw ArgumentError('bytes');
} else if (PngImageSizeData.matches(bytes)) {
return PngImageSizeData._(byteData);
} else if (GifImageSizeData.matches(bytes)) {
return GifImageSizeData._(byteData);
} else if (JpegImageSizeData.matches(bytes)) {
return JpegImageSizeData._fromBytes(file);
} else if (WebPImageSizeData.matches(bytes)) {
return WebPImageSizeData._(byteData);
} else if (BmpImageSizeData.matches(bytes)) {
return BmpImageSizeData._(byteData);
} else {
return BmpImageSizeData._(byteData);
}
}
/// The [ImageFormat] this instance represents.
final ImageFormat format;
/// The width, in pixels, of the image.
///
/// If the image is multi-frame, this is the width of the first frame.
final int width;
/// The height, in pixels, of the image.
///
/// If the image is multi-frame, this is the height of the first frame.
final int height;
/// The estimated size of the image in bytes.
///
/// The `withMipmapping` parameter controls whether to account for mipmapping
/// when decompressing the image. Flutter will use this when possible, at the
/// cost of slightly more memory usage.
int decodedSizeInBytes({bool withMipmapping = true}) {
if (withMipmapping) {
return (width * height * 4.3).ceil();
}
return width * height * 4;
}
}
/// The [ImageSizeData] for a PNG image.
class PngImageSizeData extends ImageSizeData {
PngImageSizeData._(ByteData data)
: super(
format: ImageFormat.png,
width: data.getUint32(16, Endian.big),
height: data.getUint32(20, Endian.big),
);
/// Returns true if `bytes` starts with the expected header for a PNG image.
static bool matches(Uint8List bytes) {
return bytes.lengthInBytes > 20 &&
bytes[0] == 0x89 &&
bytes[1] == 0x50 &&
bytes[2] == 0x4E &&
bytes[3] == 0x47 &&
bytes[4] == 0x0D &&
bytes[5] == 0x0A &&
bytes[6] == 0x1A &&
bytes[7] == 0x0A;
}
}
/// The [ImageSizeData] for a GIF image.
class GifImageSizeData extends ImageSizeData {
GifImageSizeData._(ByteData data)
: super(
format: ImageFormat.gif,
width: data.getUint16(6, Endian.little),
height: data.getUint16(8, Endian.little),
);
/// Returns true if `bytes` starts with the expected header for a GIF image.
static bool matches(Uint8List bytes) {
return bytes.lengthInBytes > 8 &&
bytes[0] == 0x47 &&
bytes[1] == 0x49 &&
bytes[2] == 0x46 &&
bytes[3] == 0x38 &&
(bytes[4] == 0x37 || bytes[4] == 0x39) // 7 or 9
&&
bytes[5] == 0x61;
}
}
/// The [ImageSizeData] for a JPEG image.
///
/// This library does not support JPEG2000 images.
class JpegImageSizeData extends ImageSizeData {
JpegImageSizeData._({required super.width, required super.height})
: super(
format: ImageFormat.jpeg,
);
factory JpegImageSizeData._fromBytes(file) {
ByteData data = file.readAsBytesSync().buffer.asByteData();
int start = 2;
BlockEntity? block;
var orientation = 1;
FileInput input = FileInput(File(file.path));
block = _getBlockSync(input, start);
if (block == null) {
throw Exception('Invalid jpeg file');
}
// Check for App1 block
if (block.type == 0xE1) {
final app1BlockData = input.getRange(
start,
block.start + block.length,
);
final exifOrientation = _getOrientation(app1BlockData);
if (exifOrientation != null) {
orientation = exifOrientation;
}
}
final needRotate = [5, 6, 7, 8].contains(orientation);
int index = 4; // Skip the first header bytes (already validated).
index += data.getUint16(index, Endian.big);
while (index < data.lengthInBytes) {
if (data.getUint8(index) != 0xFF) {
// Start of block
throw StateError('Invalid JPEG file${data.getUint8(index)}');
}
// if (data.getUint8(index + 1) == 0xC0) {
if ([0xC0, 0xC1, 0xC2].contains(data.getUint8(index + 1))) {
// Start of frame 0
if (needRotate) {
return JpegImageSizeData._(
height: data.getUint16(index + 7, Endian.big),
width: data.getUint16(index + 5, Endian.big),
);
} else {
return JpegImageSizeData._(
height: data.getUint16(index + 5, Endian.big),
width: data.getUint16(index + 7, Endian.big),
);
}
}
index += 2;
index += data.getUint16(index, Endian.big);
}
return JpegImageSizeData._(
height: 0,
width: 0,
);
}
/// Returns true if `bytes` starts with the expected header for a JPEG image.
static bool matches(Uint8List bytes) {
return bytes.lengthInBytes > 12 &&
bytes[0] == 0xFF &&
bytes[1] == 0xD8 &&
bytes[2] == 0xFF;
}
}
/// The [ImageSizeData] for a WebP image.
class WebPImageSizeData extends ImageSizeData {
WebPImageSizeData._(ByteData data)
: super(
format: ImageFormat.webp,
width: data.getUint16(26, Endian.little),
height: data.getUint16(28, Endian.little),
);
/// Returns true if `bytes` starts with the expected header for a WebP image.
static bool matches(Uint8List bytes) {
// ByteData bytes = file.readAsBytesSync().buffer.asByteData();
return bytes.lengthInBytes > 28 &&
bytes[0] == 0x52 // R
&&
bytes[1] == 0x49 // I
&&
bytes[2] == 0x46 // F
&&
bytes[3] == 0x46 // F
&&
bytes[8] == 0x57 // W
&&
bytes[9] == 0x45 // E
&&
bytes[10] == 0x42 // B
&&
bytes[11] == 0x50; // P
}
}
/// The [ImageSizeData] for a BMP image.
class BmpImageSizeData extends ImageSizeData {
BmpImageSizeData._(ByteData data)
: super(
format: ImageFormat.bmp,
width: data.getInt32(18, Endian.little),
height: data.getInt32(22, Endian.little));
/// Returns true if `bytes` starts with the expected header for a WebP image.
static bool matches(Uint8List bytes) {
return bytes.lengthInBytes > 22 && bytes[0] == 0x42 && bytes[1] == 0x4D;
}
}
class BlockEntity {
/// The block of jpeg format.
BlockEntity(this.type, this.length, this.start);
/// The type of the block.
int type;
/// The length of the block.
int length;
/// Start of offset
int start;
/// Error block.
static BlockEntity error = BlockEntity(-1, -1, -1);
@override
String toString() {
return "BlockEntity (type:$type, length:$length)";
}
}
BlockEntity _createBlock(
List<int> sizeList,
int blockStart,
List<int> blockInfoList,
) {
final blockLength = convertRadix16ToInt(sizeList) + 2; // +2 for 0xFF and TYPE
final typeInt = blockInfoList[1];
return BlockEntity(typeInt, blockLength, blockStart);
}
int convertRadix16ToInt(List<int> list, {bool reverse = false}) {
final sb = StringBuffer();
if (reverse) {
list = list.toList().reversed.toList();
}
for (final i in list) {
sb.write(i.toRadixString(16).padLeft(2, '0'));
}
final numString = sb.toString();
return int.tryParse(numString, radix: 16) ?? 0;
}
BlockEntity? _getBlockSync(FileInput input, int blockStart) {
try {
final blockInfoList = input.getRange(blockStart, blockStart + 4);
if (blockInfoList[0] != 0xFF) {
return null;
}
final blockSizeList = input.getRange(blockStart + 2, blockStart + 4);
return _createBlock(blockSizeList, blockStart, blockInfoList);
} catch (e) {
return null;
}
}
int? _getOrientation(List<int> app1blockData) {
// About EXIF, See: https://www.media.mit.edu/pia/Research/deepview/exif.html#orientation
// app1 block buffer:
// header (2 bytes)
// length (2 bytes)
// exif header (6 bytes)
// exif for little endian (2 bytes), 0x4d4d is for big endian, 0x4949 is for little endian
// tag mark (2 bytes)
// offset first IFD (4 bytes)
// IFD data :
// number of entries (2 bytes)
// for each entry:
// exif tag (2 bytes)
// data format (2 bytes), 1 = unsigned byte, 2 = ascii, 3 = unsigned short, 4 = unsigned long, 5 = unsigned rational, 6 = signed byte, 7 = undefined, 8 = signed short, 9 = signed long, 10 = signed rational
// number of components (4 bytes)
// value (4 bytes)
// padding (0 ~ 3 bytes, depends on data format)
// So, the IFD data starts at offset 14.
// Check app1 block exif info is valid
if (app1blockData.length < 14) {
return null;
}
// Check app1 block exif info is valid
final exifIdentifier = app1blockData.sublist(4, 10);
const listEquality = ListEquality();
if (!listEquality
.equals(exifIdentifier, [0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
return null;
}
final littleEndian = app1blockData[10] == 0x49;
int getNumber(int start, int end) {
final numberList = app1blockData.sublist(start, end);
return convertRadix16ToInt(numberList, reverse: littleEndian);
}
// Get idf byte
var idf0Start = 18;
final tagEntryCount = getNumber(idf0Start, idf0Start + 2);
var currentIndex = idf0Start + 2;
for (var i = 0; i < tagEntryCount; i++) {
final tagType = getNumber(currentIndex, currentIndex + 2);
if (tagType == 0x0112) {
return getNumber(currentIndex + 8, currentIndex + 10);
}
// every tag length is 0xC bytes
currentIndex += 0xC;
}
return null;
}
class FileInput {
/// {@macro image_size_getter.file_input}
const FileInput(this.file);
final File file;
List<int> getRange(int start, int end) {
final utils = FileUtils(file);
return utils.getRangeSync(start, end);
}
int get length => file.lengthSync();
bool exists() {
return file.existsSync();
}
}
class FileUtils {
/// {@macro image_size_getter.FileUtils}
FileUtils(this.file);
/// The file.
File file;
/// {@macro image_size_getter.FileUtils.getRangeSync}
Future<List<int>> getRange(int start, int end) async {
return getRangeSync(start, end);
}
List<int> getRangeSync(int start, int end) {
final accessFile = file.openSync();
try {
accessFile.setPositionSync(start);
return accessFile.readSync(end - start).toList();
} finally {
accessFile.closeSync();
}
}
}
I'm having a similar problem with the attached image. The solution above by @vihatsoft looks like it might work, but I would need to modify it quite a bit for JPEG images as our solution needs to work with bytes as well as files. I would also prefer a solution integrated into the current library as we've been using it for some time.
I am also having the same issues when using camera photos
Try to use new version: 2.2.0.
Also see: https://github.com/CaiJingLong/dart_image_size_getter/issues/36#issuecomment-2398761408