dart_image_size_getter icon indicating copy to clipboard operation
dart_image_size_getter copied to clipboard

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

Open vihatsoft opened this issue 1 year ago • 2 comments

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?

vihatsoft avatar Aug 29 '24 07:08 vihatsoft

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

dodatw avatar Sep 06 '24 09:09 dodatw

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();
    }
  }
}

vihatsoft avatar Sep 06 '24 11:09 vihatsoft

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

ansells avatar Sep 24 '24 19:09 ansells

I am also having the same issues when using camera photos

mk48 avatar Oct 02 '24 14:10 mk48

Try to use new version: 2.2.0.

Also see: https://github.com/CaiJingLong/dart_image_size_getter/issues/36#issuecomment-2398761408

CaiJingLong avatar Oct 08 '24 03:10 CaiJingLong