1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "ItemTable"
19
20 #include <ItemTable.h>
21 #include <media/DataSourceBase.h>
22 #include <media/stagefright/MetaData.h>
23 #include <media/stagefright/MediaErrors.h>
24 #include <media/stagefright/foundation/ABuffer.h>
25 #include <media/stagefright/foundation/ByteUtils.h>
26 #include <media/stagefright/foundation/hexdump.h>
27 #include <media/stagefright/foundation/MediaDefs.h>
28 #include <utils/Log.h>
29
30 namespace android {
31
32 namespace heif {
33
34 /////////////////////////////////////////////////////////////////////
35 //
36 // struct to keep track of one image item
37 //
38
39 struct ImageItem {
40 friend struct ItemReference;
41 friend struct ItemProperty;
42
ImageItemandroid::heif::ImageItem43 ImageItem() : ImageItem(0, 0, false) {}
ImageItemandroid::heif::ImageItem44 ImageItem(uint32_t _type, uint32_t _id, bool _hidden) :
45 type(_type), itemId(_id), hidden(_hidden),
46 rows(0), columns(0), width(0), height(0), rotation(0),
47 offset(0), size(0), nextTileIndex(0) {}
48
isGridandroid::heif::ImageItem49 bool isGrid() const {
50 return type == FOURCC('g', 'r', 'i', 'd');
51 }
52
getNextTileItemIdandroid::heif::ImageItem53 status_t getNextTileItemId(uint32_t *nextTileItemId, bool reset) {
54 if (reset) {
55 nextTileIndex = 0;
56 }
57 if (nextTileIndex >= dimgRefs.size()) {
58 return ERROR_END_OF_STREAM;
59 }
60 *nextTileItemId = dimgRefs[nextTileIndex++];
61 return OK;
62 }
63
64 uint32_t type;
65 uint32_t itemId;
66 bool hidden;
67 int32_t rows;
68 int32_t columns;
69 int32_t width;
70 int32_t height;
71 int32_t rotation;
72 off64_t offset;
73 size_t size;
74 sp<ABuffer> hvcc;
75 sp<ABuffer> icc;
76
77 Vector<uint32_t> thumbnails;
78 Vector<uint32_t> dimgRefs;
79 Vector<uint32_t> cdscRefs;
80 size_t nextTileIndex;
81 };
82
83 struct ExifItem {
84 off64_t offset;
85 size_t size;
86 };
87
88 /////////////////////////////////////////////////////////////////////
89 //
90 // ISO boxes
91 //
92
93 struct Box {
94 protected:
Boxandroid::heif::Box95 Box(DataSourceBase *source, uint32_t type) :
96 mDataSource(source), mType(type) {}
97
~Boxandroid::heif::Box98 virtual ~Box() {}
99
onChunkDataandroid::heif::Box100 virtual status_t onChunkData(
101 uint32_t /*type*/, off64_t /*offset*/, size_t /*size*/) {
102 return OK;
103 }
104
typeandroid::heif::Box105 inline uint32_t type() const { return mType; }
106
sourceandroid::heif::Box107 inline DataSourceBase *source() const { return mDataSource; }
108
109 status_t parseChunk(off64_t *offset);
110
111 status_t parseChunks(off64_t offset, size_t size);
112
113 private:
114 DataSourceBase *mDataSource;
115 uint32_t mType;
116 };
117
parseChunk(off64_t * offset)118 status_t Box::parseChunk(off64_t *offset) {
119 if (*offset < 0) {
120 ALOGE("b/23540914");
121 return ERROR_MALFORMED;
122 }
123 uint32_t hdr[2];
124 if (mDataSource->readAt(*offset, hdr, 8) < 8) {
125 return ERROR_IO;
126 }
127 uint64_t chunk_size = ntohl(hdr[0]);
128 int32_t chunk_type = ntohl(hdr[1]);
129 off64_t data_offset = *offset + 8;
130
131 if (chunk_size == 1) {
132 if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
133 return ERROR_IO;
134 }
135 chunk_size = ntoh64(chunk_size);
136 data_offset += 8;
137
138 if (chunk_size < 16) {
139 // The smallest valid chunk is 16 bytes long in this case.
140 return ERROR_MALFORMED;
141 }
142 } else if (chunk_size == 0) {
143 // This shouldn't happen since we should never be top level
144 ALOGE("invalid chunk size 0 for non-top level box");
145 return ERROR_MALFORMED;
146 } else if (chunk_size < 8) {
147 // The smallest valid chunk is 8 bytes long.
148 ALOGE("invalid chunk size: %lld", (long long)chunk_size);
149 return ERROR_MALFORMED;
150 }
151
152 char chunk[5];
153 MakeFourCCString(chunk_type, chunk);
154 ALOGV("chunk: %s @ %lld", chunk, (long long)*offset);
155
156 off64_t chunk_data_size = chunk_size - (data_offset - *offset);
157 if (chunk_data_size < 0) {
158 ALOGE("b/23540914");
159 return ERROR_MALFORMED;
160 }
161
162 status_t err = onChunkData(chunk_type, data_offset, chunk_data_size);
163
164 if (err != OK) {
165 return err;
166 }
167 *offset += chunk_size;
168 return OK;
169 }
170
parseChunks(off64_t offset,size_t size)171 status_t Box::parseChunks(off64_t offset, size_t size) {
172 off64_t stopOffset = offset + size;
173 while (offset < stopOffset) {
174 status_t err = parseChunk(&offset);
175 if (err != OK) {
176 return err;
177 }
178 }
179 if (offset != stopOffset) {
180 return ERROR_MALFORMED;
181 }
182 return OK;
183 }
184
185 ///////////////////////////////////////////////////////////////////////
186
187 struct FullBox : public Box {
188 protected:
FullBoxandroid::heif::FullBox189 FullBox(DataSourceBase *source, uint32_t type) :
190 Box(source, type), mVersion(0), mFlags(0) {}
191
versionandroid::heif::FullBox192 inline uint8_t version() const { return mVersion; }
193
flagsandroid::heif::FullBox194 inline uint32_t flags() const { return mFlags; }
195
196 status_t parseFullBoxHeader(off64_t *offset, size_t *size);
197
198 private:
199 uint8_t mVersion;
200 uint32_t mFlags;
201 };
202
parseFullBoxHeader(off64_t * offset,size_t * size)203 status_t FullBox::parseFullBoxHeader(off64_t *offset, size_t *size) {
204 if (*size < 4) {
205 return ERROR_MALFORMED;
206 }
207 if (!source()->readAt(*offset, &mVersion, 1)) {
208 return ERROR_IO;
209 }
210 if (!source()->getUInt24(*offset + 1, &mFlags)) {
211 return ERROR_IO;
212 }
213 *offset += 4;
214 *size -= 4;
215 return OK;
216 }
217
218 /////////////////////////////////////////////////////////////////////
219 //
220 // PrimaryImage box
221 //
222
223 struct PitmBox : public FullBox {
PitmBoxandroid::heif::PitmBox224 PitmBox(DataSourceBase *source) :
225 FullBox(source, FOURCC('p', 'i', 't', 'm')) {}
226
227 status_t parse(off64_t offset, size_t size, uint32_t *primaryItemId);
228 };
229
parse(off64_t offset,size_t size,uint32_t * primaryItemId)230 status_t PitmBox::parse(off64_t offset, size_t size, uint32_t *primaryItemId) {
231 status_t err = parseFullBoxHeader(&offset, &size);
232 if (err != OK) {
233 return err;
234 }
235
236 size_t itemIdSize = (version() == 0) ? 2 : 4;
237 if (size < itemIdSize) {
238 return ERROR_MALFORMED;
239 }
240 uint32_t itemId;
241 if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
242 return ERROR_IO;
243 }
244
245 ALOGV("primary id %d", itemId);
246 *primaryItemId = itemId;
247
248 return OK;
249 }
250
251 /////////////////////////////////////////////////////////////////////
252 //
253 // ItemLocation related boxes
254 //
255
256 struct ExtentEntry {
257 uint64_t extentIndex;
258 uint64_t extentOffset;
259 uint64_t extentLength;
260 };
261
262 struct ItemLoc {
ItemLocandroid::heif::ItemLoc263 ItemLoc() : ItemLoc(0, 0, 0, 0) {}
ItemLocandroid::heif::ItemLoc264 ItemLoc(uint32_t item_id, uint16_t construction_method,
265 uint16_t data_reference_index, uint64_t base_offset) :
266 itemId(item_id),
267 constructionMethod(construction_method),
268 dataReferenceIndex(data_reference_index),
269 baseOffset(base_offset) {}
270
addExtentandroid::heif::ItemLoc271 void addExtent(const ExtentEntry& extent) {
272 extents.push_back(extent);
273 }
274
getLocandroid::heif::ItemLoc275 status_t getLoc(off64_t *offset, size_t *size,
276 off64_t idatOffset, size_t idatSize) const {
277 // TODO: fix extent handling, fix constructionMethod = 2
278 CHECK(extents.size() == 1);
279 if (constructionMethod == 0) {
280 *offset = baseOffset + extents[0].extentOffset;
281 *size = extents[0].extentLength;
282 return OK;
283 } else if (constructionMethod == 1) {
284 if (baseOffset + extents[0].extentOffset + extents[0].extentLength
285 > idatSize) {
286 return ERROR_MALFORMED;
287 }
288 *offset = baseOffset + extents[0].extentOffset + idatOffset;
289 *size = extents[0].extentLength;
290 return OK;
291 }
292 return ERROR_UNSUPPORTED;
293 }
294
295 // parsed info
296 uint32_t itemId;
297 uint16_t constructionMethod;
298 uint16_t dataReferenceIndex;
299 off64_t baseOffset;
300 Vector<ExtentEntry> extents;
301 };
302
303 struct IlocBox : public FullBox {
IlocBoxandroid::heif::IlocBox304 IlocBox(DataSourceBase *source, KeyedVector<uint32_t, ItemLoc> *itemLocs) :
305 FullBox(source, FOURCC('i', 'l', 'o', 'c')),
306 mItemLocs(itemLocs), mHasConstructMethod1(false) {}
307
308 status_t parse(off64_t offset, size_t size);
309
hasConstructMethod1android::heif::IlocBox310 bool hasConstructMethod1() { return mHasConstructMethod1; }
311
312 private:
isSizeFieldValidandroid::heif::IlocBox313 static bool isSizeFieldValid(uint32_t offset_size) {
314 return offset_size == 0 || offset_size == 4 || offset_size == 8;
315 }
316 KeyedVector<uint32_t, ItemLoc> *mItemLocs;
317 bool mHasConstructMethod1;
318 };
319
parse(off64_t offset,size_t size)320 status_t IlocBox::parse(off64_t offset, size_t size) {
321 status_t err = parseFullBoxHeader(&offset, &size);
322 if (err != OK) {
323 return err;
324 }
325 if (version() > 2) {
326 ALOGE("%s: invalid version %d", __FUNCTION__, version());
327 return ERROR_MALFORMED;
328 }
329
330 if (size < 2) {
331 return ERROR_MALFORMED;
332 }
333 uint8_t offset_size;
334 if (!source()->readAt(offset++, &offset_size, 1)) {
335 return ERROR_IO;
336 }
337 uint8_t length_size = (offset_size & 0xF);
338 offset_size >>= 4;
339
340 uint8_t base_offset_size;
341 if (!source()->readAt(offset++, &base_offset_size, 1)) {
342 return ERROR_IO;
343 }
344 uint8_t index_size = 0;
345 if (version() == 1 || version() == 2) {
346 index_size = (base_offset_size & 0xF);
347 }
348 base_offset_size >>= 4;
349 size -= 2;
350
351 if (!isSizeFieldValid(offset_size)
352 || !isSizeFieldValid(length_size)
353 || !isSizeFieldValid(base_offset_size)
354 || !isSizeFieldValid((index_size))) {
355 ALOGE("%s: offset size not valid: %d, %d, %d, %d", __FUNCTION__,
356 offset_size, length_size, base_offset_size, index_size);
357 return ERROR_MALFORMED;
358 }
359
360 uint32_t item_count;
361 size_t itemFieldSize = version() < 2 ? 2 : 4;
362 if (size < itemFieldSize) {
363 return ERROR_MALFORMED;
364 }
365 if (!source()->getUInt32Var(offset, &item_count, itemFieldSize)) {
366 return ERROR_IO;
367 }
368
369 ALOGV("item_count %lld", (long long) item_count);
370 offset += itemFieldSize;
371 size -= itemFieldSize;
372
373 for (size_t i = 0; i < item_count; i++) {
374 uint32_t item_id;
375 if (!source()->getUInt32Var(offset, &item_id, itemFieldSize)) {
376 return ERROR_IO;
377 }
378 ALOGV("item[%zu]: id %lld", i, (long long)item_id);
379 offset += itemFieldSize;
380
381 uint8_t construction_method = 0;
382 if (version() == 1 || version() == 2) {
383 uint8_t buf[2];
384 if (!source()->readAt(offset, buf, 2)) {
385 return ERROR_IO;
386 }
387 construction_method = (buf[1] & 0xF);
388 ALOGV("construction_method %d", construction_method);
389 if (construction_method == 1) {
390 mHasConstructMethod1 = true;
391 }
392
393 offset += 2;
394 }
395
396 uint16_t data_reference_index;
397 if (!source()->getUInt16(offset, &data_reference_index)) {
398 return ERROR_IO;
399 }
400 ALOGV("data_reference_index %d", data_reference_index);
401 if (data_reference_index != 0) {
402 // we don't support reference to other files
403 return ERROR_UNSUPPORTED;
404 }
405 offset += 2;
406
407 uint64_t base_offset = 0;
408 if (base_offset_size != 0) {
409 if (!source()->getUInt64Var(offset, &base_offset, base_offset_size)) {
410 return ERROR_IO;
411 }
412 offset += base_offset_size;
413 }
414 ALOGV("base_offset %lld", (long long) base_offset);
415
416 ssize_t index = mItemLocs->add(item_id, ItemLoc(
417 item_id, construction_method, data_reference_index, base_offset));
418 ItemLoc &item = mItemLocs->editValueAt(index);
419
420 uint16_t extent_count;
421 if (!source()->getUInt16(offset, &extent_count)) {
422 return ERROR_IO;
423 }
424 ALOGV("extent_count %d", extent_count);
425
426 if (extent_count > 1 && (offset_size == 0 || length_size == 0)) {
427 // if the item is dividec into more than one extents, offset and
428 // length must be present.
429 return ERROR_MALFORMED;
430 }
431 offset += 2;
432
433 for (size_t j = 0; j < extent_count; j++) {
434 uint64_t extent_index = 1; // default=1
435 if ((version() == 1 || version() == 2) && (index_size > 0)) {
436 if (!source()->getUInt64Var(offset, &extent_index, index_size)) {
437 return ERROR_IO;
438 }
439 // TODO: add support for this mode
440 offset += index_size;
441 ALOGV("extent_index %lld", (long long)extent_index);
442 }
443
444 uint64_t extent_offset = 0; // default=0
445 if (offset_size > 0) {
446 if (!source()->getUInt64Var(offset, &extent_offset, offset_size)) {
447 return ERROR_IO;
448 }
449 offset += offset_size;
450 }
451 ALOGV("extent_offset %lld", (long long)extent_offset);
452
453 uint64_t extent_length = 0; // this indicates full length of file
454 if (length_size > 0) {
455 if (!source()->getUInt64Var(offset, &extent_length, length_size)) {
456 return ERROR_IO;
457 }
458 offset += length_size;
459 }
460 ALOGV("extent_length %lld", (long long)extent_length);
461
462 item.addExtent({ extent_index, extent_offset, extent_length });
463 }
464 }
465 return OK;
466 }
467
468 /////////////////////////////////////////////////////////////////////
469 //
470 // ItemReference related boxes
471 //
472
473 struct ItemReference : public Box, public RefBase {
ItemReferenceandroid::heif::ItemReference474 ItemReference(DataSourceBase *source, uint32_t type, uint32_t itemIdSize) :
475 Box(source, type), mItemId(0), mRefIdSize(itemIdSize) {}
476
477 status_t parse(off64_t offset, size_t size);
478
itemIdandroid::heif::ItemReference479 uint32_t itemId() { return mItemId; }
480
481 void apply(
482 KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
483 KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const;
484
485 private:
486 uint32_t mItemId;
487 uint32_t mRefIdSize;
488 Vector<uint32_t> mRefs;
489
490 DISALLOW_EVIL_CONSTRUCTORS(ItemReference);
491 };
492
apply(KeyedVector<uint32_t,ImageItem> & itemIdToItemMap,KeyedVector<uint32_t,ExifItem> & itemIdToExifMap) const493 void ItemReference::apply(
494 KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
495 KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const {
496 ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId);
497
498 switch(type()) {
499 case FOURCC('d', 'i', 'm', 'g'): {
500 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
501
502 // ignore non-image items
503 if (itemIndex < 0) {
504 return;
505 }
506
507 ImageItem &derivedImage = itemIdToItemMap.editValueAt(itemIndex);
508 if (!derivedImage.dimgRefs.empty()) {
509 ALOGW("dimgRefs not clean!");
510 }
511 derivedImage.dimgRefs.appendVector(mRefs);
512
513 for (size_t i = 0; i < mRefs.size(); i++) {
514 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
515
516 // ignore non-image items
517 if (itemIndex < 0) {
518 continue;
519 }
520 ImageItem &sourceImage = itemIdToItemMap.editValueAt(itemIndex);
521
522 // mark the source image of the derivation as hidden
523 sourceImage.hidden = true;
524 }
525 break;
526 }
527 case FOURCC('t', 'h', 'm', 'b'): {
528 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
529
530 // ignore non-image items
531 if (itemIndex < 0) {
532 return;
533 }
534
535 // mark thumbnail image as hidden, these can be retrieved if the client
536 // request thumbnail explicitly, but won't be exposed as displayables.
537 ImageItem &thumbImage = itemIdToItemMap.editValueAt(itemIndex);
538 thumbImage.hidden = true;
539
540 for (size_t i = 0; i < mRefs.size(); i++) {
541 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
542
543 // ignore non-image items
544 if (itemIndex < 0) {
545 continue;
546 }
547 ALOGV("Image item id %d uses thumbnail item id %d", mRefs[i], mItemId);
548 ImageItem &masterImage = itemIdToItemMap.editValueAt(itemIndex);
549 if (!masterImage.thumbnails.empty()) {
550 ALOGW("already has thumbnails!");
551 }
552 masterImage.thumbnails.push_back(mItemId);
553 }
554 break;
555 }
556 case FOURCC('c', 'd', 's', 'c'): {
557 ssize_t itemIndex = itemIdToExifMap.indexOfKey(mItemId);
558
559 // ignore non-exif block items
560 if (itemIndex < 0) {
561 return;
562 }
563
564 for (size_t i = 0; i < mRefs.size(); i++) {
565 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
566
567 // ignore non-image items
568 if (itemIndex < 0) {
569 continue;
570 }
571 ALOGV("Image item id %d uses metadata item id %d", mRefs[i], mItemId);
572 ImageItem &image = itemIdToItemMap.editValueAt(itemIndex);
573 image.cdscRefs.push_back(mItemId);
574 }
575 break;
576 }
577 case FOURCC('a', 'u', 'x', 'l'): {
578 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
579
580 // ignore non-image items
581 if (itemIndex < 0) {
582 return;
583 }
584
585 // mark auxiliary image as hidden
586 ImageItem &auxImage = itemIdToItemMap.editValueAt(itemIndex);
587 auxImage.hidden = true;
588 break;
589 }
590 default:
591 ALOGW("ignoring unsupported ref type 0x%x", type());
592 }
593 }
594
parse(off64_t offset,size_t size)595 status_t ItemReference::parse(off64_t offset, size_t size) {
596 if (size < mRefIdSize + 2) {
597 return ERROR_MALFORMED;
598 }
599 if (!source()->getUInt32Var(offset, &mItemId, mRefIdSize)) {
600 return ERROR_IO;
601 }
602 offset += mRefIdSize;
603
604 uint16_t count;
605 if (!source()->getUInt16(offset, &count)) {
606 return ERROR_IO;
607 }
608 offset += 2;
609 size -= (mRefIdSize + 2);
610
611 if (size < count * mRefIdSize) {
612 return ERROR_MALFORMED;
613 }
614
615 for (size_t i = 0; i < count; i++) {
616 uint32_t refItemId;
617 if (!source()->getUInt32Var(offset, &refItemId, mRefIdSize)) {
618 return ERROR_IO;
619 }
620 offset += mRefIdSize;
621 mRefs.push_back(refItemId);
622 ALOGV("item id %d: referencing item id %d", mItemId, refItemId);
623 }
624
625 return OK;
626 }
627
628 struct IrefBox : public FullBox {
IrefBoxandroid::heif::IrefBox629 IrefBox(DataSourceBase *source, Vector<sp<ItemReference> > *itemRefs) :
630 FullBox(source, FOURCC('i', 'r', 'e', 'f')), mRefIdSize(0), mItemRefs(itemRefs) {}
631
632 status_t parse(off64_t offset, size_t size);
633
634 protected:
635 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
636
637 private:
638 uint32_t mRefIdSize;
639 Vector<sp<ItemReference> > *mItemRefs;
640 };
641
parse(off64_t offset,size_t size)642 status_t IrefBox::parse(off64_t offset, size_t size) {
643 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
644 status_t err = parseFullBoxHeader(&offset, &size);
645 if (err != OK) {
646 return err;
647 }
648
649 mRefIdSize = (version() == 0) ? 2 : 4;
650 return parseChunks(offset, size);
651 }
652
onChunkData(uint32_t type,off64_t offset,size_t size)653 status_t IrefBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
654 sp<ItemReference> itemRef = new ItemReference(source(), type, mRefIdSize);
655
656 status_t err = itemRef->parse(offset, size);
657 if (err != OK) {
658 return err;
659 }
660 mItemRefs->push_back(itemRef);
661 return OK;
662 }
663
664 /////////////////////////////////////////////////////////////////////
665 //
666 // ItemProperty related boxes
667 //
668
669 struct AssociationEntry {
670 uint32_t itemId;
671 bool essential;
672 uint16_t index;
673 };
674
675 struct ItemProperty : public RefBase {
ItemPropertyandroid::heif::ItemProperty676 ItemProperty() {}
677
attachToandroid::heif::ItemProperty678 virtual void attachTo(ImageItem &/*image*/) const {
679 ALOGW("Unrecognized property");
680 }
parseandroid::heif::ItemProperty681 virtual status_t parse(off64_t /*offset*/, size_t /*size*/) {
682 ALOGW("Unrecognized property");
683 return OK;
684 }
685
686 private:
687 DISALLOW_EVIL_CONSTRUCTORS(ItemProperty);
688 };
689
690 struct IspeBox : public FullBox, public ItemProperty {
IspeBoxandroid::heif::IspeBox691 IspeBox(DataSourceBase *source) :
692 FullBox(source, FOURCC('i', 's', 'p', 'e')), mWidth(0), mHeight(0) {}
693
694 status_t parse(off64_t offset, size_t size) override;
695
attachToandroid::heif::IspeBox696 void attachTo(ImageItem &image) const override {
697 image.width = mWidth;
698 image.height = mHeight;
699 }
700
701 private:
702 uint32_t mWidth;
703 uint32_t mHeight;
704 };
705
parse(off64_t offset,size_t size)706 status_t IspeBox::parse(off64_t offset, size_t size) {
707 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
708
709 status_t err = parseFullBoxHeader(&offset, &size);
710 if (err != OK) {
711 return err;
712 }
713
714 if (size < 8) {
715 return ERROR_MALFORMED;
716 }
717 if (!source()->getUInt32(offset, &mWidth)
718 || !source()->getUInt32(offset + 4, &mHeight)) {
719 return ERROR_IO;
720 }
721 ALOGV("property ispe: %dx%d", mWidth, mHeight);
722
723 return OK;
724 }
725
726 struct HvccBox : public Box, public ItemProperty {
HvccBoxandroid::heif::HvccBox727 HvccBox(DataSourceBase *source) :
728 Box(source, FOURCC('h', 'v', 'c', 'C')) {}
729
730 status_t parse(off64_t offset, size_t size) override;
731
attachToandroid::heif::HvccBox732 void attachTo(ImageItem &image) const override {
733 image.hvcc = mHVCC;
734 }
735
736 private:
737 sp<ABuffer> mHVCC;
738 };
739
parse(off64_t offset,size_t size)740 status_t HvccBox::parse(off64_t offset, size_t size) {
741 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
742
743 mHVCC = new ABuffer(size);
744
745 if (mHVCC->data() == NULL) {
746 ALOGE("b/28471206");
747 return NO_MEMORY;
748 }
749
750 if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) {
751 return ERROR_IO;
752 }
753
754 ALOGV("property hvcC");
755
756 return OK;
757 }
758
759 struct IrotBox : public Box, public ItemProperty {
IrotBoxandroid::heif::IrotBox760 IrotBox(DataSourceBase *source) :
761 Box(source, FOURCC('i', 'r', 'o', 't')), mAngle(0) {}
762
763 status_t parse(off64_t offset, size_t size) override;
764
attachToandroid::heif::IrotBox765 void attachTo(ImageItem &image) const override {
766 image.rotation = mAngle * 90;
767 }
768
769 private:
770 uint8_t mAngle;
771 };
772
parse(off64_t offset,size_t size)773 status_t IrotBox::parse(off64_t offset, size_t size) {
774 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
775
776 if (size < 1) {
777 return ERROR_MALFORMED;
778 }
779 if (source()->readAt(offset, &mAngle, 1) != 1) {
780 return ERROR_IO;
781 }
782 mAngle &= 0x3;
783 ALOGV("property irot: %d", mAngle);
784
785 return OK;
786 }
787
788 struct ColrBox : public Box, public ItemProperty {
ColrBoxandroid::heif::ColrBox789 ColrBox(DataSourceBase *source) :
790 Box(source, FOURCC('c', 'o', 'l', 'r')) {}
791
792 status_t parse(off64_t offset, size_t size) override;
793
attachToandroid::heif::ColrBox794 void attachTo(ImageItem &image) const override {
795 image.icc = mICCData;
796 }
797
798 private:
799 sp<ABuffer> mICCData;
800 };
801
parse(off64_t offset,size_t size)802 status_t ColrBox::parse(off64_t offset, size_t size) {
803 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
804
805 if (size < 4) {
806 return ERROR_MALFORMED;
807 }
808 uint32_t colour_type;
809 if (!source()->getUInt32(offset, &colour_type)) {
810 return ERROR_IO;
811 }
812 offset += 4;
813 size -= 4;
814 if (colour_type == FOURCC('n', 'c', 'l', 'x')) {
815 return OK;
816 }
817 if ((colour_type != FOURCC('r', 'I', 'C', 'C')) &&
818 (colour_type != FOURCC('p', 'r', 'o', 'f'))) {
819 return ERROR_MALFORMED;
820 }
821
822 mICCData = new ABuffer(size);
823 if (mICCData->data() == NULL) {
824 ALOGE("b/28471206");
825 return NO_MEMORY;
826 }
827
828 if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) {
829 return ERROR_IO;
830 }
831
832 ALOGV("property Colr: size %zd", size);
833 return OK;
834 }
835
836 struct IpmaBox : public FullBox {
IpmaBoxandroid::heif::IpmaBox837 IpmaBox(DataSourceBase *source, Vector<AssociationEntry> *associations) :
838 FullBox(source, FOURCC('i', 'p', 'm', 'a')), mAssociations(associations) {}
839
840 status_t parse(off64_t offset, size_t size);
841 private:
842 Vector<AssociationEntry> *mAssociations;
843 };
844
parse(off64_t offset,size_t size)845 status_t IpmaBox::parse(off64_t offset, size_t size) {
846 status_t err = parseFullBoxHeader(&offset, &size);
847 if (err != OK) {
848 return err;
849 }
850
851 if (size < 4) {
852 return ERROR_MALFORMED;
853 }
854 uint32_t entryCount;
855 if (!source()->getUInt32(offset, &entryCount)) {
856 return ERROR_IO;
857 }
858 offset += 4;
859 size -= 4;
860
861 for (size_t k = 0; k < entryCount; ++k) {
862 uint32_t itemId = 0;
863 size_t itemIdSize = (version() < 1) ? 2 : 4;
864
865 if (size < itemIdSize + 1) {
866 return ERROR_MALFORMED;
867 }
868
869 if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
870 return ERROR_IO;
871 }
872 offset += itemIdSize;
873 size -= itemIdSize;
874
875 uint8_t associationCount;
876 if (!source()->readAt(offset, &associationCount, 1)) {
877 return ERROR_IO;
878 }
879 offset++;
880 size--;
881
882 for (size_t i = 0; i < associationCount; ++i) {
883 size_t propIndexSize = (flags() & 1) ? 2 : 1;
884 if (size < propIndexSize) {
885 return ERROR_MALFORMED;
886 }
887 uint16_t propIndex;
888 if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) {
889 return ERROR_IO;
890 }
891 offset += propIndexSize;
892 size -= propIndexSize;
893 uint16_t bitmask = (1 << (8 * propIndexSize - 1));
894 AssociationEntry entry = {
895 .itemId = itemId,
896 .essential = !!(propIndex & bitmask),
897 .index = (uint16_t) (propIndex & ~bitmask)
898 };
899
900 ALOGV("item id %d associated to property %d (essential %d)",
901 itemId, entry.index, entry.essential);
902
903 mAssociations->push_back(entry);
904 }
905 }
906
907 return OK;
908 }
909
910 struct IpcoBox : public Box {
IpcoBoxandroid::heif::IpcoBox911 IpcoBox(DataSourceBase *source, Vector<sp<ItemProperty> > *properties) :
912 Box(source, FOURCC('i', 'p', 'c', 'o')), mItemProperties(properties) {}
913
914 status_t parse(off64_t offset, size_t size);
915 protected:
916 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
917
918 private:
919 Vector<sp<ItemProperty> > *mItemProperties;
920 };
921
parse(off64_t offset,size_t size)922 status_t IpcoBox::parse(off64_t offset, size_t size) {
923 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
924 // push dummy as the index is 1-based
925 mItemProperties->push_back(new ItemProperty());
926 return parseChunks(offset, size);
927 }
928
onChunkData(uint32_t type,off64_t offset,size_t size)929 status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
930 sp<ItemProperty> itemProperty;
931 switch(type) {
932 case FOURCC('h', 'v', 'c', 'C'):
933 {
934 itemProperty = new HvccBox(source());
935 break;
936 }
937 case FOURCC('i', 's', 'p', 'e'):
938 {
939 itemProperty = new IspeBox(source());
940 break;
941 }
942 case FOURCC('i', 'r', 'o', 't'):
943 {
944 itemProperty = new IrotBox(source());
945 break;
946 }
947 case FOURCC('c', 'o', 'l', 'r'):
948 {
949 itemProperty = new ColrBox(source());
950 break;
951 }
952 default:
953 {
954 // push dummy to maintain correct item property index
955 itemProperty = new ItemProperty();
956 break;
957 }
958 }
959 status_t err = itemProperty->parse(offset, size);
960 if (err != OK) {
961 return err;
962 }
963 mItemProperties->push_back(itemProperty);
964 return OK;
965 }
966
967 struct IprpBox : public Box {
IprpBoxandroid::heif::IprpBox968 IprpBox(DataSourceBase *source,
969 Vector<sp<ItemProperty> > *properties,
970 Vector<AssociationEntry> *associations) :
971 Box(source, FOURCC('i', 'p', 'r', 'p')),
972 mProperties(properties), mAssociations(associations) {}
973
974 status_t parse(off64_t offset, size_t size);
975 protected:
976 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
977
978 private:
979 Vector<sp<ItemProperty> > *mProperties;
980 Vector<AssociationEntry> *mAssociations;
981 };
982
parse(off64_t offset,size_t size)983 status_t IprpBox::parse(off64_t offset, size_t size) {
984 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
985
986 status_t err = parseChunks(offset, size);
987 if (err != OK) {
988 return err;
989 }
990 return OK;
991 }
992
onChunkData(uint32_t type,off64_t offset,size_t size)993 status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
994 switch(type) {
995 case FOURCC('i', 'p', 'c', 'o'):
996 {
997 IpcoBox ipcoBox(source(), mProperties);
998 return ipcoBox.parse(offset, size);
999 }
1000 case FOURCC('i', 'p', 'm', 'a'):
1001 {
1002 IpmaBox ipmaBox(source(), mAssociations);
1003 return ipmaBox.parse(offset, size);
1004 }
1005 default:
1006 {
1007 ALOGW("Unrecognized box.");
1008 break;
1009 }
1010 }
1011 return OK;
1012 }
1013
1014 /////////////////////////////////////////////////////////////////////
1015 //
1016 // ItemInfo related boxes
1017 //
1018 struct ItemInfo {
1019 uint32_t itemId;
1020 uint32_t itemType;
1021 bool hidden;
1022 };
1023
1024 struct InfeBox : public FullBox {
InfeBoxandroid::heif::InfeBox1025 InfeBox(DataSourceBase *source) :
1026 FullBox(source, FOURCC('i', 'n', 'f', 'e')) {}
1027
1028 status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo);
1029
1030 private:
1031 bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out);
1032 };
1033
parseNullTerminatedString(off64_t * offset,size_t * size,String8 * out)1034 bool InfeBox::parseNullTerminatedString(
1035 off64_t *offset, size_t *size, String8 *out) {
1036 char tmp;
1037 Vector<char> buf;
1038 buf.setCapacity(256);
1039 off64_t newOffset = *offset;
1040 off64_t stopOffset = *offset + *size;
1041 while (newOffset < stopOffset) {
1042 if (!source()->readAt(newOffset++, &tmp, 1)) {
1043 return false;
1044 }
1045 buf.push_back(tmp);
1046 if (tmp == 0) {
1047 out->setTo(buf.array());
1048
1049 *offset = newOffset;
1050 *size = stopOffset - newOffset;
1051
1052 return true;
1053 }
1054 }
1055 return false;
1056 }
1057
parse(off64_t offset,size_t size,ItemInfo * itemInfo)1058 status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) {
1059 status_t err = parseFullBoxHeader(&offset, &size);
1060 if (err != OK) {
1061 return err;
1062 }
1063
1064 if (version() == 0 || version() == 1) {
1065 return ERROR_UNSUPPORTED;
1066 } else { // version >= 2
1067 uint32_t item_id;
1068 size_t itemIdSize = (version() == 2) ? 2 : 4;
1069 if (size < itemIdSize + 6) {
1070 return ERROR_MALFORMED;
1071 }
1072 if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) {
1073 return ERROR_IO;
1074 }
1075 ALOGV("item_id %d", item_id);
1076 offset += itemIdSize;
1077 uint16_t item_protection_index;
1078 if (!source()->getUInt16(offset, &item_protection_index)) {
1079 return ERROR_IO;
1080 }
1081 ALOGV("item_protection_index %d", item_protection_index);
1082 offset += 2;
1083 uint32_t item_type;
1084 if (!source()->getUInt32(offset, &item_type)) {
1085 return ERROR_IO;
1086 }
1087
1088 itemInfo->itemId = item_id;
1089 itemInfo->itemType = item_type;
1090 // According to HEIF spec, (flags & 1) indicates the image is hidden
1091 // and not supposed to be displayed.
1092 itemInfo->hidden = (flags() & 1);
1093
1094 char itemTypeString[5];
1095 MakeFourCCString(item_type, itemTypeString);
1096 ALOGV("item_type %s", itemTypeString);
1097 offset += 4;
1098 size -= itemIdSize + 6;
1099
1100 String8 item_name;
1101 if (!parseNullTerminatedString(&offset, &size, &item_name)) {
1102 return ERROR_MALFORMED;
1103 }
1104 ALOGV("item_name %s", item_name.c_str());
1105
1106 if (item_type == FOURCC('m', 'i', 'm', 'e')) {
1107 String8 content_type;
1108 if (!parseNullTerminatedString(&offset, &size, &content_type)) {
1109 return ERROR_MALFORMED;
1110 }
1111
1112 // content_encoding is optional; can be omitted if would be empty
1113 if (size > 0) {
1114 String8 content_encoding;
1115 if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
1116 return ERROR_MALFORMED;
1117 }
1118 }
1119 } else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
1120 String8 item_uri_type;
1121 if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) {
1122 return ERROR_MALFORMED;
1123 }
1124 }
1125 }
1126 return OK;
1127 }
1128
1129 struct IinfBox : public FullBox {
IinfBoxandroid::heif::IinfBox1130 IinfBox(DataSourceBase *source, Vector<ItemInfo> *itemInfos) :
1131 FullBox(source, FOURCC('i', 'i', 'n', 'f')),
1132 mItemInfos(itemInfos), mHasGrids(false) {}
1133
1134 status_t parse(off64_t offset, size_t size);
1135
hasGridsandroid::heif::IinfBox1136 bool hasGrids() { return mHasGrids; }
1137
1138 protected:
1139 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
1140
1141 private:
1142 Vector<ItemInfo> *mItemInfos;
1143 bool mHasGrids;
1144 };
1145
parse(off64_t offset,size_t size)1146 status_t IinfBox::parse(off64_t offset, size_t size) {
1147 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1148
1149 status_t err = parseFullBoxHeader(&offset, &size);
1150 if (err != OK) {
1151 return err;
1152 }
1153
1154 size_t entryCountSize = version() == 0 ? 2 : 4;
1155 if (size < entryCountSize) {
1156 return ERROR_MALFORMED;
1157 }
1158 uint32_t entry_count;
1159 if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) {
1160 return ERROR_IO;
1161 }
1162 ALOGV("entry_count %d", entry_count);
1163
1164 off64_t stopOffset = offset + size;
1165 offset += entryCountSize;
1166 for (size_t i = 0; i < entry_count && offset < stopOffset; i++) {
1167 ALOGV("entry %zu", i);
1168 status_t err = parseChunk(&offset);
1169 if (err != OK) {
1170 return err;
1171 }
1172 }
1173 if (offset != stopOffset) {
1174 return ERROR_MALFORMED;
1175 }
1176
1177 return OK;
1178 }
1179
onChunkData(uint32_t type,off64_t offset,size_t size)1180 status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
1181 if (type != FOURCC('i', 'n', 'f', 'e')) {
1182 return OK;
1183 }
1184
1185 InfeBox infeBox(source());
1186 ItemInfo itemInfo;
1187 status_t err = infeBox.parse(offset, size, &itemInfo);
1188 if (err == OK) {
1189 mItemInfos->push_back(itemInfo);
1190 mHasGrids |= (itemInfo.itemType == FOURCC('g', 'r', 'i', 'd'));
1191 }
1192 // InfeBox parse returns ERROR_UNSUPPORTED if the box if an unsupported
1193 // version. Ignore this error as it's not fatal.
1194 return (err == ERROR_UNSUPPORTED) ? OK : err;
1195 }
1196
1197 //////////////////////////////////////////////////////////////////
1198
ItemTable(DataSourceBase * source)1199 ItemTable::ItemTable(DataSourceBase *source)
1200 : mDataSource(source),
1201 mPrimaryItemId(0),
1202 mIdatOffset(0),
1203 mIdatSize(0),
1204 mImageItemsValid(false),
1205 mCurrentItemIndex(0) {
1206 mRequiredBoxes.insert('iprp');
1207 mRequiredBoxes.insert('iloc');
1208 mRequiredBoxes.insert('pitm');
1209 mRequiredBoxes.insert('iinf');
1210 }
1211
~ItemTable()1212 ItemTable::~ItemTable() {}
1213
parse(uint32_t type,off64_t data_offset,size_t chunk_data_size)1214 status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) {
1215 switch(type) {
1216 case FOURCC('i', 'l', 'o', 'c'):
1217 {
1218 return parseIlocBox(data_offset, chunk_data_size);
1219 }
1220 case FOURCC('i', 'i', 'n', 'f'):
1221 {
1222 return parseIinfBox(data_offset, chunk_data_size);
1223 }
1224 case FOURCC('i', 'p', 'r', 'p'):
1225 {
1226 return parseIprpBox(data_offset, chunk_data_size);
1227 }
1228 case FOURCC('p', 'i', 't', 'm'):
1229 {
1230 return parsePitmBox(data_offset, chunk_data_size);
1231 }
1232 case FOURCC('i', 'd', 'a', 't'):
1233 {
1234 return parseIdatBox(data_offset, chunk_data_size);
1235 }
1236 case FOURCC('i', 'r', 'e', 'f'):
1237 {
1238 return parseIrefBox(data_offset, chunk_data_size);
1239 }
1240 case FOURCC('i', 'p', 'r', 'o'):
1241 {
1242 ALOGW("ipro box not supported!");
1243 break;
1244 }
1245 default:
1246 {
1247 ALOGW("unrecognized box type: 0x%x", type);
1248 break;
1249 }
1250 }
1251 return ERROR_UNSUPPORTED;
1252 }
1253
parseIlocBox(off64_t offset,size_t size)1254 status_t ItemTable::parseIlocBox(off64_t offset, size_t size) {
1255 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1256
1257 IlocBox ilocBox(mDataSource, &mItemLocs);
1258 status_t err = ilocBox.parse(offset, size);
1259 if (err != OK) {
1260 return err;
1261 }
1262
1263 if (ilocBox.hasConstructMethod1()) {
1264 mRequiredBoxes.insert('idat');
1265 }
1266
1267 return buildImageItemsIfPossible('iloc');
1268 }
1269
parseIinfBox(off64_t offset,size_t size)1270 status_t ItemTable::parseIinfBox(off64_t offset, size_t size) {
1271 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1272
1273 IinfBox iinfBox(mDataSource, &mItemInfos);
1274 status_t err = iinfBox.parse(offset, size);
1275 if (err != OK) {
1276 return err;
1277 }
1278
1279 if (iinfBox.hasGrids()) {
1280 mRequiredBoxes.insert('iref');
1281 }
1282
1283 return buildImageItemsIfPossible('iinf');
1284 }
1285
parsePitmBox(off64_t offset,size_t size)1286 status_t ItemTable::parsePitmBox(off64_t offset, size_t size) {
1287 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1288
1289 PitmBox pitmBox(mDataSource);
1290 status_t err = pitmBox.parse(offset, size, &mPrimaryItemId);
1291 if (err != OK) {
1292 return err;
1293 }
1294
1295 return buildImageItemsIfPossible('pitm');
1296 }
1297
parseIprpBox(off64_t offset,size_t size)1298 status_t ItemTable::parseIprpBox(off64_t offset, size_t size) {
1299 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1300
1301 IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations);
1302 status_t err = iprpBox.parse(offset, size);
1303 if (err != OK) {
1304 return err;
1305 }
1306
1307 return buildImageItemsIfPossible('iprp');
1308 }
1309
parseIdatBox(off64_t offset,size_t size)1310 status_t ItemTable::parseIdatBox(off64_t offset, size_t size) {
1311 ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1312
1313 // only remember the offset and size of idat box for later use
1314 mIdatOffset = offset;
1315 mIdatSize = size;
1316
1317 return buildImageItemsIfPossible('idat');
1318 }
1319
parseIrefBox(off64_t offset,size_t size)1320 status_t ItemTable::parseIrefBox(off64_t offset, size_t size) {
1321 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1322
1323 IrefBox irefBox(mDataSource, &mItemReferences);
1324 status_t err = irefBox.parse(offset, size);
1325 if (err != OK) {
1326 return err;
1327 }
1328
1329 return buildImageItemsIfPossible('iref');
1330 }
1331
buildImageItemsIfPossible(uint32_t type)1332 status_t ItemTable::buildImageItemsIfPossible(uint32_t type) {
1333 if (mImageItemsValid) {
1334 return OK;
1335 }
1336
1337 mBoxesSeen.insert(type);
1338
1339 // need at least 'iprp', 'iloc', 'pitm', 'iinf';
1340 // need 'idat' if any items used construction_method of 2;
1341 // need 'iref' if there are grids.
1342 if (!std::includes(
1343 mBoxesSeen.begin(), mBoxesSeen.end(),
1344 mRequiredBoxes.begin(), mRequiredBoxes.end())) {
1345 return OK;
1346 }
1347
1348 ALOGV("building image table...");
1349
1350 for (size_t i = 0; i < mItemInfos.size(); i++) {
1351 const ItemInfo &info = mItemInfos[i];
1352
1353 // Only handle 3 types of items, all others are ignored:
1354 // 'grid': derived image from tiles
1355 // 'hvc1': coded image (or tile)
1356 // 'Exif': EXIF metadata
1357 if (info.itemType != FOURCC('g', 'r', 'i', 'd') &&
1358 info.itemType != FOURCC('h', 'v', 'c', '1') &&
1359 info.itemType != FOURCC('E', 'x', 'i', 'f')) {
1360 continue;
1361 }
1362
1363 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(info.itemId);
1364 if (itemIndex >= 0) {
1365 ALOGW("ignoring duplicate image item id %d", info.itemId);
1366 continue;
1367 }
1368
1369 ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId);
1370 if (ilocIndex < 0) {
1371 ALOGE("iloc missing for image item id %d", info.itemId);
1372 continue;
1373 }
1374 const ItemLoc &iloc = mItemLocs[ilocIndex];
1375
1376 off64_t offset;
1377 size_t size;
1378 if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) {
1379 return ERROR_MALFORMED;
1380 }
1381
1382 if (info.itemType == FOURCC('E', 'x', 'i', 'f')) {
1383 // Only add if the Exif data is non-empty. The first 4 bytes contain
1384 // the offset to TIFF header, which the Exif parser doesn't use.
1385 if (size > 4) {
1386 ExifItem exifItem = {
1387 .offset = offset,
1388 .size = size,
1389 };
1390 mItemIdToExifMap.add(info.itemId, exifItem);
1391 }
1392 continue;
1393 }
1394
1395 ImageItem image(info.itemType, info.itemId, info.hidden);
1396
1397 ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId);
1398
1399 if (image.isGrid()) {
1400 // ImageGrid struct is at least 8-byte, at most 12-byte (if flags&1)
1401 if (size < 8 || size > 12) {
1402 return ERROR_MALFORMED;
1403 }
1404 uint8_t buf[12];
1405 if (!mDataSource->readAt(offset, buf, size)) {
1406 return ERROR_IO;
1407 }
1408
1409 image.rows = buf[2] + 1;
1410 image.columns = buf[3] + 1;
1411
1412 ALOGV("rows %d, columans %d", image.rows, image.columns);
1413 } else {
1414 image.offset = offset;
1415 image.size = size;
1416 }
1417 mItemIdToItemMap.add(info.itemId, image);
1418 }
1419
1420 for (size_t i = 0; i < mAssociations.size(); i++) {
1421 attachProperty(mAssociations[i]);
1422 }
1423
1424 for (size_t i = 0; i < mItemReferences.size(); i++) {
1425 mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToExifMap);
1426 }
1427
1428 bool foundPrimary = false;
1429 for (size_t i = 0; i < mItemIdToItemMap.size(); i++) {
1430 // add all non-hidden images, also add the primary even if it's marked
1431 // hidden, in case the primary is set to a thumbnail
1432 bool isPrimary = (mItemIdToItemMap[i].itemId == mPrimaryItemId);
1433 if (!mItemIdToItemMap[i].hidden || isPrimary) {
1434 mDisplayables.push_back(i);
1435 }
1436 foundPrimary |= isPrimary;
1437 }
1438
1439 ALOGV("found %zu displayables", mDisplayables.size());
1440
1441 // fail if no displayables are found
1442 if (mDisplayables.empty()) {
1443 return ERROR_MALFORMED;
1444 }
1445
1446 // if the primary item id is invalid, set primary to the first displayable
1447 if (!foundPrimary) {
1448 mPrimaryItemId = mItemIdToItemMap[mDisplayables[0]].itemId;
1449 }
1450
1451 mImageItemsValid = true;
1452 return OK;
1453 }
1454
attachProperty(const AssociationEntry & association)1455 void ItemTable::attachProperty(const AssociationEntry &association) {
1456 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(association.itemId);
1457
1458 // ignore non-image items
1459 if (itemIndex < 0) {
1460 return;
1461 }
1462
1463 uint16_t propertyIndex = association.index;
1464 if (propertyIndex >= mItemProperties.size()) {
1465 ALOGW("Ignoring invalid property index %d", propertyIndex);
1466 return;
1467 }
1468
1469 ALOGV("attach property %d to item id %d)",
1470 propertyIndex, association.itemId);
1471
1472 mItemProperties[propertyIndex]->attachTo(mItemIdToItemMap.editValueAt(itemIndex));
1473 }
1474
countImages() const1475 uint32_t ItemTable::countImages() const {
1476 return mImageItemsValid ? mDisplayables.size() : 0;
1477 }
1478
getImageMeta(const uint32_t imageIndex)1479 sp<MetaData> ItemTable::getImageMeta(const uint32_t imageIndex) {
1480 if (!mImageItemsValid) {
1481 return NULL;
1482 }
1483
1484 if (imageIndex >= mDisplayables.size()) {
1485 ALOGE("%s: invalid image index %u", __FUNCTION__, imageIndex);
1486 return NULL;
1487 }
1488 const uint32_t itemIndex = mDisplayables[imageIndex];
1489 ALOGV("image[%u]: item index %u", imageIndex, itemIndex);
1490
1491 const ImageItem *image = &mItemIdToItemMap[itemIndex];
1492
1493 ssize_t tileItemIndex = -1;
1494 if (image->isGrid()) {
1495 if (image->dimgRefs.empty()) {
1496 return NULL;
1497 }
1498 tileItemIndex = mItemIdToItemMap.indexOfKey(image->dimgRefs[0]);
1499 if (tileItemIndex < 0) {
1500 return NULL;
1501 }
1502 }
1503
1504 sp<MetaData> meta = new MetaData;
1505 meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC);
1506
1507 if (image->itemId == mPrimaryItemId) {
1508 meta->setInt32(kKeyTrackIsDefault, 1);
1509 }
1510
1511 ALOGV("image[%u]: size %dx%d", imageIndex, image->width, image->height);
1512
1513 meta->setInt32(kKeyWidth, image->width);
1514 meta->setInt32(kKeyHeight, image->height);
1515 if (image->rotation != 0) {
1516 // Rotation angle in HEIF is CCW, convert to CW here to be
1517 // consistent with the other media formats.
1518 switch(image->rotation) {
1519 case 90: meta->setInt32(kKeyRotation, 270); break;
1520 case 180: meta->setInt32(kKeyRotation, 180); break;
1521 case 270: meta->setInt32(kKeyRotation, 90); break;
1522 default: break; // don't set if invalid
1523 }
1524 }
1525 meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
1526
1527 if (!image->thumbnails.empty()) {
1528 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(image->thumbnails[0]);
1529 if (thumbItemIndex >= 0) {
1530 const ImageItem &thumbnail = mItemIdToItemMap[thumbItemIndex];
1531
1532 meta->setInt32(kKeyThumbnailWidth, thumbnail.width);
1533 meta->setInt32(kKeyThumbnailHeight, thumbnail.height);
1534 meta->setData(kKeyThumbnailHVCC, kTypeHVCC,
1535 thumbnail.hvcc->data(), thumbnail.hvcc->size());
1536 ALOGV("image[%u]: thumbnail: size %dx%d, item index %zd",
1537 imageIndex, thumbnail.width, thumbnail.height, thumbItemIndex);
1538 } else {
1539 ALOGW("%s: Referenced thumbnail does not exist!", __FUNCTION__);
1540 }
1541 }
1542
1543 if (image->isGrid()) {
1544 meta->setInt32(kKeyGridRows, image->rows);
1545 meta->setInt32(kKeyGridCols, image->columns);
1546
1547 // point image to the first tile for grid size and HVCC
1548 image = &mItemIdToItemMap.editValueAt(tileItemIndex);
1549 meta->setInt32(kKeyTileWidth, image->width);
1550 meta->setInt32(kKeyTileHeight, image->height);
1551 meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
1552 }
1553
1554 if (image->hvcc == NULL) {
1555 ALOGE("%s: hvcc is missing for image[%u]!", __FUNCTION__, imageIndex);
1556 return NULL;
1557 }
1558 meta->setData(kKeyHVCC, kTypeHVCC, image->hvcc->data(), image->hvcc->size());
1559
1560 if (image->icc != NULL) {
1561 meta->setData(kKeyIccProfile, 0, image->icc->data(), image->icc->size());
1562 }
1563 return meta;
1564 }
1565
findImageItem(const uint32_t imageIndex,uint32_t * itemIndex)1566 status_t ItemTable::findImageItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1567 if (!mImageItemsValid) {
1568 return INVALID_OPERATION;
1569 }
1570
1571 if (imageIndex >= mDisplayables.size()) {
1572 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1573 return BAD_VALUE;
1574 }
1575
1576 *itemIndex = mDisplayables[imageIndex];
1577
1578 ALOGV("image[%u]: item index %u", imageIndex, *itemIndex);
1579 return OK;
1580 }
1581
findThumbnailItem(const uint32_t imageIndex,uint32_t * itemIndex)1582 status_t ItemTable::findThumbnailItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1583 if (!mImageItemsValid) {
1584 return INVALID_OPERATION;
1585 }
1586
1587 if (imageIndex >= mDisplayables.size()) {
1588 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1589 return BAD_VALUE;
1590 }
1591
1592 uint32_t masterItemIndex = mDisplayables[imageIndex];
1593
1594 const ImageItem &masterImage = mItemIdToItemMap[masterItemIndex];
1595 if (masterImage.thumbnails.empty()) {
1596 *itemIndex = masterItemIndex;
1597 return OK;
1598 }
1599
1600 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(masterImage.thumbnails[0]);
1601 if (thumbItemIndex < 0) {
1602 // Do not return the master image in this case, fail it so that the
1603 // thumbnail extraction code knows we really don't have it.
1604 return INVALID_OPERATION;
1605 }
1606
1607 *itemIndex = thumbItemIndex;
1608 return OK;
1609 }
1610
getImageOffsetAndSize(uint32_t * itemIndex,off64_t * offset,size_t * size)1611 status_t ItemTable::getImageOffsetAndSize(
1612 uint32_t *itemIndex, off64_t *offset, size_t *size) {
1613 if (!mImageItemsValid) {
1614 return INVALID_OPERATION;
1615 }
1616
1617 if (itemIndex != NULL) {
1618 if (*itemIndex >= mItemIdToItemMap.size()) {
1619 ALOGE("%s: Bad item index!", __FUNCTION__);
1620 return BAD_VALUE;
1621 }
1622 mCurrentItemIndex = *itemIndex;
1623 }
1624
1625 ImageItem &image = mItemIdToItemMap.editValueAt(mCurrentItemIndex);
1626 if (image.isGrid()) {
1627 uint32_t tileItemId;
1628 status_t err = image.getNextTileItemId(&tileItemId, itemIndex != NULL);
1629 if (err != OK) {
1630 return err;
1631 }
1632 ssize_t tileItemIndex = mItemIdToItemMap.indexOfKey(tileItemId);
1633 if (tileItemIndex < 0) {
1634 return ERROR_END_OF_STREAM;
1635 }
1636 *offset = mItemIdToItemMap[tileItemIndex].offset;
1637 *size = mItemIdToItemMap[tileItemIndex].size;
1638 } else {
1639 if (itemIndex == NULL) {
1640 // For single images, we only allow it to be read once, after that
1641 // it's EOS. New item index must be requested each time.
1642 return ERROR_END_OF_STREAM;
1643 }
1644 *offset = mItemIdToItemMap[mCurrentItemIndex].offset;
1645 *size = mItemIdToItemMap[mCurrentItemIndex].size;
1646 }
1647
1648 return OK;
1649 }
1650
getExifOffsetAndSize(off64_t * offset,size_t * size)1651 status_t ItemTable::getExifOffsetAndSize(off64_t *offset, size_t *size) {
1652 if (!mImageItemsValid) {
1653 return INVALID_OPERATION;
1654 }
1655
1656 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(mPrimaryItemId);
1657
1658 // this should not happen, something's seriously wrong.
1659 if (itemIndex < 0) {
1660 return INVALID_OPERATION;
1661 }
1662
1663 const ImageItem &image = mItemIdToItemMap[itemIndex];
1664 if (image.cdscRefs.size() == 0) {
1665 return NAME_NOT_FOUND;
1666 }
1667
1668 ssize_t exifIndex = mItemIdToExifMap.indexOfKey(image.cdscRefs[0]);
1669 if (exifIndex < 0) {
1670 return NAME_NOT_FOUND;
1671 }
1672
1673 // skip the first 4-byte of the offset to TIFF header
1674 *offset = mItemIdToExifMap[exifIndex].offset + 4;
1675 *size = mItemIdToExifMap[exifIndex].size - 4;
1676 return OK;
1677 }
1678
1679 } // namespace heif
1680
1681 } // namespace android
1682