Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add needRotate for Size #20

Merged
merged 2 commits into from Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added example/asset/have_orientation_exif_3.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/asset/have_orientation_exif_6.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 112 additions & 15 deletions library/lib/src/decoder/impl/jpeg_decoder.dart
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:image_size_getter/image_size_getter.dart';
import 'package:image_size_getter/src/entity/block_entity.dart';

Expand All @@ -17,6 +18,7 @@ class JpegDecoder extends BaseDecoder with SimpleTypeValidator {
Size getSize(ImageInput input) {
int start = 2;
BlockEntity? block;
var orientation = 1;

while (true) {
block = _getBlockSync(input, start);
Expand All @@ -25,26 +27,41 @@ class JpegDecoder extends BaseDecoder with SimpleTypeValidator {
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;
print('Orientation: $orientation');
}
}

if (block.type == 0xC0 || block.type == 0xC2) {
final widthList = input.getRange(start + 7, start + 9);
final heightList = input.getRange(start + 5, start + 7);
return _getSize(widthList, heightList);
return _getSize(widthList, heightList, orientation);
} else {
start += block.length;
}
}
}

Size _getSize(List<int> widthList, List<int> heightList) {
Size _getSize(List<int> widthList, List<int> heightList, int orientation) {
final width = convertRadix16ToInt(widthList);
final height = convertRadix16ToInt(heightList);
return Size(width, height);
final needRotate = [5, 6, 7, 8].contains(orientation);
return Size(width, height, needRotate: needRotate);
}

@override
Future<Size> getSizeAsync(AsyncImageInput input) async {
int start = 2;
BlockEntity? block;
var orientation = 1;

while (true) {
block = await _getBlockAsync(input, start);
Expand All @@ -53,59 +70,139 @@ class JpegDecoder extends BaseDecoder with SimpleTypeValidator {
throw Exception('Invalid jpeg file');
}

if (block.type == 0xE1) {
final app1BlockData = await input.getRange(
start,
block.start + block.length,
);
final exifOrientation = _getOrientation(app1BlockData);
if (exifOrientation != null) {
orientation = exifOrientation;
print('Orientation: $orientation');
}
}

if (block.type == 0xC0 || block.type == 0xC2) {
final widthList = await input.getRange(start + 7, start + 9);
final heightList = await input.getRange(start + 5, start + 7);
return _getSize(widthList, heightList);
final orientation = (await input.getRange(start + 9, start + 10))[0];
return _getSize(widthList, heightList, orientation);
} else {
start += block.length;
}
}
}

BlockEntity? _getBlockSync(ImageInput input, int blackStart) {
BlockEntity? _getBlockSync(ImageInput input, int blockStart) {
try {
final blockInfoList = input.getRange(blackStart, blackStart + 4);
final blockInfoList = input.getRange(blockStart, blockStart + 4);

if (blockInfoList[0] != 0xFF) {
return null;
}

final radix16List = input.getRange(blackStart + 2, blackStart + 4);
final blockSizeList = input.getRange(blockStart + 2, blockStart + 4);

return _createBlock(radix16List, blackStart, blockInfoList);
return _createBlock(blockSizeList, blockStart, blockInfoList);
} catch (e) {
return null;
}
}

Future<BlockEntity?> _getBlockAsync(
AsyncImageInput input, int blackStart) async {
AsyncImageInput input, int blockStart) async {
try {
final blockInfoList = await input.getRange(blackStart, blackStart + 4);
final blockInfoList = await input.getRange(blockStart, blockStart + 4);

if (blockInfoList[0] != 0xFF) {
return null;
}

final sizeList = await input.getRange(blackStart + 2, blackStart + 4);
final blockSizeList =
await input.getRange(blockStart + 2, blockStart + 4);

return _createBlock(sizeList, blackStart, blockInfoList);
return _createBlock(blockSizeList, blockStart, blockInfoList);
} catch (e) {
return null;
}
}

BlockEntity _createBlock(
List<int> sizeList, int blackStart, List<int> blockInfoList) {
final blockLength = convertRadix16ToInt(sizeList) + 2;
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);
return BlockEntity(typeInt, blockLength, blockStart);
}

@override
SimpleFileHeaderAndFooter get simpleFileHeaderAndFooter => _JpegInfo();

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

final 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 _JpegInfo with SimpleFileHeaderAndFooter {
Expand Down
7 changes: 5 additions & 2 deletions library/lib/src/entity/block_entity.dart
@@ -1,16 +1,19 @@
/// The block of jpeg format.
class BlockEntity {
/// The block of jpeg format.
BlockEntity(this.type, this.length);
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);
static BlockEntity error = BlockEntity(-1, -1, -1);

@override
String toString() {
Expand Down
32 changes: 29 additions & 3 deletions library/lib/src/entity/size.dart
Expand Up @@ -7,21 +7,44 @@ import 'package:hashcodes/hashcodes.dart';
/// The size contains [width] and [height].
///
/// {@endtemplate}
///
/// ---
///
/// {@macro image_size_getter.Size.needToRotate}
class Size {
const Size(this.width, this.height);
/// {@macro image_size_getter.Size}
///
/// ---
///
/// {@macro image_size_getter.Size.needToRotate}
const Size(
this.width,
this.height, {
this.needRotate = false,
});

/// The width of the media.
final int width;

/// The height of the media.
final int height;

/// {@template image_size_getter.Size.needToRotate}
///
/// If the [needRotate] is true,
/// the [width] and [height] need to be swapped when using.
///
/// Such as, orientation value of the jpeg format is [5, 6, 7, 8].
///
/// {@endtemplate}
final bool needRotate;

/// The [width] is zero and [height] is zero.
static Size zero = Size(0, 0);

@override
String toString() {
return "Size( $width, $height )";
return "Size( $width, $height, needRotate: $needRotate )";
}

@override
Expand All @@ -35,8 +58,11 @@ class Size {
}

if (obj is Size) {
return width == obj.width && height == obj.height;
return width == obj.width &&
height == obj.height &&
needRotate == obj.needRotate;
}

return false;
}

Expand Down
17 changes: 17 additions & 0 deletions library/test/image_size_getter_test.dart
Expand Up @@ -55,6 +55,23 @@ void main() {
assert(decoder.isValid(input));
expect(decoder.getSize(input), Size(256, 256));
});

test('Test have orientation jpeg', () {
final orientation3 = File('../example/asset/have_orientation_exif_3.jpg');

const JpegDecoder decoder = JpegDecoder();
final input = FileInput(orientation3);

assert(decoder.isValid(input));
expect(decoder.getSize(input), Size(533, 799));

final orientation6 = File('../example/asset/have_orientation_exif_6.jpg');
final input2 = FileInput(orientation6);

assert(decoder.isValid(input2));
final size = decoder.getSize(input2);
expect(size, Size(3264, 2448, needRotate: true));
});
});

group('Test get size.', () {
Expand Down