CaiJingLong / dart_image_size_getter

Use dart file api to get image size, needn't use decode. just read the metadata.
Apache License 2.0
64 stars 30 forks source link

[Discussions] Unhandled Exception: Unsupported operation: The input is not supported. #39

Open vihatsoft opened 1 month ago

vihatsoft commented 1 month ago

Content

when select jpg image get error for some files. final size = ImageSizeGetter.getSize(FileInput(file));

image

why get error with jpg file that take from camera?

dodatw commented 3 weeks ago

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 ?

5_2body_portrait

vihatsoft commented 3 weeks ago

Use this. working with your image.

var info = ImageSizeData.fromBytes(file);
info.width
info.height
info.format

/// 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 sizeList, int blockStart, List blockInfoList, ) { final blockLength = convertRadix16ToInt(sizeList) + 2; // +2 for 0xFF and TYPE final typeInt = blockInfoList[1];

return BlockEntity(typeInt, blockLength, blockStart); }

int convertRadix16ToInt(List 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 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 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> getRange(int start, int end) async { return getRangeSync(start, end); }

List getRangeSync(int start, int end) { final accessFile = file.openSync(); try { accessFile.setPositionSync(start); return accessFile.readSync(end - start).toList(); } finally { accessFile.closeSync(); } } }

ansells commented 1 week ago

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.

CCM_Upload

mk48 commented 1 day ago

I am also having the same issues when using camera photos