-
-
Notifications
You must be signed in to change notification settings - Fork 29
/
jpeg_decoder.dart
217 lines (176 loc) · 6.06 KB
/
jpeg_decoder.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import 'package:collection/collection.dart';
import 'package:image_size_getter/image_size_getter.dart';
import 'package:image_size_getter/src/entity/block_entity.dart';
/// {@template image_size_getter.JpegDecoder}
///
/// [JpegDecoder] is a class for decoding JPEG image.
///
/// {@endtemplate}
class JpegDecoder extends BaseDecoder with SimpleTypeValidator {
/// {@macro image_size_getter.JpegDecoder}
const JpegDecoder();
@override
String get decoderName => 'jpeg';
@override
Size getSize(ImageInput input) {
int start = 2;
BlockEntity? block;
var orientation = 1;
while (true) {
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;
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, orientation);
} else {
start += block.length;
}
}
}
Size _getSize(List<int> widthList, List<int> heightList, int orientation) {
final width = convertRadix16ToInt(widthList);
final height = convertRadix16ToInt(heightList);
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);
if (block == null) {
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);
final orientation = (await input.getRange(start + 9, start + 10))[0];
return _getSize(widthList, heightList, orientation);
} else {
start += block.length;
}
}
}
BlockEntity? _getBlockSync(ImageInput 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;
}
}
Future<BlockEntity?> _getBlockAsync(
AsyncImageInput input, int blockStart) async {
try {
final blockInfoList = await input.getRange(blockStart, blockStart + 4);
if (blockInfoList[0] != 0xFF) {
return null;
}
final blockSizeList =
await input.getRange(blockStart + 2, blockStart + 4);
return _createBlock(blockSizeList, blockStart, blockInfoList);
} catch (e) {
return null;
}
}
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);
}
@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 {
static const start = [0xFF, 0xD8];
static const end = [0xFF, 0xD9];
@override
List<int> get endBytes => end;
@override
List<int> get startBytes => start;
}