aboutsummaryrefslogtreecommitdiff
path: root/src/image_type_recognition/image_type_recognition_lite.cc
blob: 5976f420c189fd890bb3919352900615e04167f1 (plain)
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
// Copyright 2015 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
//
// This file implements the image type recognition algorithm. Functions, which
// will check each single image type, are implemented based on the comparisons
// of magic numbers or signature strings. Other checks (e.g endianness, general
// tiff magic number "42", etc.) could also be used in some of those functions
// to make the type recognition more stable. Those checks are designed
// according to the format spcifications and our own experiments. Notice that
// the magic numbers and signature strings may have different binary values
// according to different endiannesses.
#include "src/image_type_recognition/image_type_recognition_lite.h"

#include <algorithm>
#include <cassert>
#include <string>
#include <vector>

#include "src/binary_parse/range_checked_byte_ptr.h"

namespace piex {
namespace image_type_recognition {
namespace {

using std::string;
using binary_parse::MemoryStatus;
using binary_parse::RangeCheckedBytePtr;

// Base class for checking image type. For each image type, one should create an
// inherited class and do the implementation.
class TypeChecker {
 public:
  // Comparing function, whihc is used for sorting.
  static bool Compare(const TypeChecker* a, const TypeChecker* b) {
    assert(a);
    assert(b);
    return a->RequestedSize() < b->RequestedSize();
  }

  virtual ~TypeChecker() {}

  // Returns the type of current checker.
  virtual RawImageTypes Type() const = 0;

  // Returns the requested data size (in bytes) for current checker. The checker
  // guarantees that it will not read more than this size.
  virtual size_t RequestedSize() const = 0;

  // Checks if source data belongs to current checker type.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const = 0;

 protected:
  // Limits the source length to the RequestedSize(), using it guarantees that
  // we will not read more than this size from the source.
  RangeCheckedBytePtr LimitSource(const RangeCheckedBytePtr& source) const {
    return source.pointerToSubArray(0 /* pos */, RequestedSize());
  }
};

// Check if the uint16 value at (source + offset) is equal to the target value.
bool CheckUInt16Value(const RangeCheckedBytePtr& source,
                      const size_t source_offset, const bool use_big_endian,
                      const unsigned short target_value) {  // NOLINT
  MemoryStatus status = binary_parse::RANGE_CHECKED_BYTE_SUCCESS;
  const unsigned short value = binary_parse::Get16u(  // NOLINT
      source + source_offset, use_big_endian, &status);
  if (status != binary_parse::RANGE_CHECKED_BYTE_SUCCESS) {
    return false;
  }
  return (target_value == value);
}

// Check if the uint32 value at (source + offset) is equal to the target value.
bool CheckUInt32Value(const RangeCheckedBytePtr& source,
                      const size_t source_offset, const bool use_big_endian,
                      const unsigned int target_value) {
  MemoryStatus status = binary_parse::RANGE_CHECKED_BYTE_SUCCESS;
  const unsigned int value =
      binary_parse::Get32u(source + source_offset, use_big_endian, &status);
  if (status != binary_parse::RANGE_CHECKED_BYTE_SUCCESS) {
    return false;
  }
  return (target_value == value);
}

// Determine the endianness. The return value is NOT the endianness indicator,
// it's just that this function was successful.
bool DetermineEndianness(const RangeCheckedBytePtr& source,
                         bool* is_big_endian) {
  if (source.remainingLength() < 2) {
    return false;
  }

  if (source[0] == 0x49 && source[1] == 0x49) {
    *is_big_endian = false;
  } else if (source[0] == 0x4D && source[1] == 0x4D) {
    *is_big_endian = true;
  } else {
    return false;
  }
  return true;
}

// Check if signature string can match to the same length string start from
// (source + offset). The signature string will be used as longer magic number
// series.
bool IsSignatureMatched(const RangeCheckedBytePtr& source,
                        const size_t source_offset, const string& signature) {
  return source.substr(source_offset, signature.size()) == signature;
}

// Check if signature is found in [source + offset, source + offset + range].
bool IsSignatureFound(const RangeCheckedBytePtr& source,
                      const size_t search_offset, const size_t search_range,
                      const string& signature, size_t* first_matched) {
  if (source.remainingLength() < search_offset + search_range) {
    return false;
  }

  // The index must be in range [offset, offset + range - sizeof(signature)], so
  // that it can guarantee that it will not read outside of range.
  for (size_t i = search_offset;
       i < search_offset + search_range - signature.size(); ++i) {
    if (IsSignatureMatched(source, i, signature)) {
      if (first_matched) {
        *first_matched = i;
      }
      return true;
    }
  }
  return false;
}

// Sony RAW format.
class ArwTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kArwImage; }

  virtual size_t RequestedSize() const { return 10000; }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. correct tiff magic number at the (offset == 8) position of the file;
  // 3. signature "SONY" in first requested bytes;
  // 4. correct signature for (section + version) in first requested bytes.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTiffMagic = 0x2A;  // NOLINT
    const unsigned int kTiffOffset = 8;
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTiffMagic) ||
        !CheckUInt32Value(limited_source, 4 /* offset */, use_big_endian,
                          kTiffOffset)) {
      return false;
    }

    // Search for kSignatureSony in first requested bytes
    const string kSignatureSony("SONY");
    if (!IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                          kSignatureSony, NULL)) {
      return false;
    }

    // Search for (kSignatureFileTypeSection + kSignatureVersions[i]) in first
    // requested bytes
    const string kSignatureSection("\x00\xb0\x01\x00\x04\x00\x00\x00", 8);
    const int kSignatureVersionsSize = 6;
    const string kSignatureVersions[kSignatureVersionsSize] = {
        string("\x02\x00", 2),  // ARW 1.0
        string("\x03\x00", 2),  // ARW 2.0
        string("\x03\x01", 2),  // ARW 2.1
        string("\x03\x02", 2),  // ARW 2.2
        string("\x03\x03", 2),  // ARW 2.3
        string("\x04\x00", 2),  // ARW 4.0
    };
    bool matched = false;
    for (int i = 0; i < kSignatureVersionsSize; ++i) {
      matched = matched || IsSignatureFound(
                               limited_source, 0 /* offset */, RequestedSize(),
                               kSignatureSection + kSignatureVersions[i], NULL);
    }
    return matched;
  }
};

// Canon RAW (CR3 extension).
class Cr3TypeChecker : public TypeChecker {
 public:
  static constexpr size_t kSignatureOffset = 4;
  static constexpr const char* kSignature = "ftypcrx ";

  virtual RawImageTypes Type() const { return kCr3Image; }

  virtual size_t RequestedSize() const {
    return kSignatureOffset + strlen(kSignature);
  }

  // Checks for the ftyp box w/ brand 'crx '.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);
    return IsSignatureMatched(limited_source, kSignatureOffset, kSignature);
  }
};

// Canon RAW (CR2 extension).
class Cr2TypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kCr2Image; }

  virtual size_t RequestedSize() const { return 16; }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. magic number "42" at the (offset == 2) position of the file;
  // 3. signature "CR2" at the (offset == 8) position of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTag = 42;  // NOLINT
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTag)) {
      return false;
    }

    const string kSignature("CR\2\0", 4);
    return IsSignatureMatched(limited_source, 8 /* offset */, kSignature);
  }
};

// Canon RAW (CRW extension).
class CrwTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kCrwImage; }

  virtual size_t RequestedSize() const { return 14; }

  // Check only the signature at the (offset == 6) position of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    string signature;
    if (use_big_endian) {
      signature = string("\x00\x10\xba\xb0\xac\xbb\x00\x02", 8);
    } else {
      signature = string("HEAPCCDR");
    }
    return IsSignatureMatched(limited_source, 6 /* offset */, signature);
  }
};

// Kodak RAW.
class DcrTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kDcrImage; }

  virtual size_t RequestedSize() const { return 5000; }

  // Check two different cases, only need to fulfill one of the two:
  // 1. signature at the (offset == 16) position of the file;
  // 2. two tags (OriginalFileName and FirmwareVersion) can be found in the
  // first requested bytes of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    // Case 1: has signature
    const string kSignature(
        "\x4b\x4f\x44\x41\x4b\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20", 16);
    if (IsSignatureMatched(limited_source, 16 /* offset */, kSignature)) {
      return true;
    }

    // Case 2: search for tags in first requested bytes
    string kIfdTags[2];
    if (use_big_endian) {
      kIfdTags[0] = string("\x03\xe9\x00\x02", 4);  // OriginalFileName
      kIfdTags[1] = string("\x0c\xe5\x00\x02", 4);  // FirmwareVersion
    } else {
      kIfdTags[0] = string("\xe9\x03\x02\x00", 4);  // OriginalFileName
      kIfdTags[1] = string("\xe5\x0c\x02\x00", 4);  // FirmwareVersion
    }
    return IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kIfdTags[0], NULL) &&
           IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kIfdTags[1], NULL);
  }
};

// Digital Negative RAW.
class DngTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kDngImage; }

  virtual size_t RequestedSize() const { return 1024; }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. at least two dng specific tags in the first requested bytes of the
  // file
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    // Search tags in first requested bytes and verify the order of them.
    const int kTagsCount = 5;
    string dng_tags[kTagsCount];
    if (use_big_endian) {
      dng_tags[0] =
          string("\xc6\x12\x00\x01\x00\x00\x00\x04", 8);  // tag: 50706
      dng_tags[1] =
          string("\xc6\x13\x00\x01\x00\x00\x00\x04", 8);  // tag: 50707
      dng_tags[2] = string("\xc6\x14\x00\x02", 4);        // tag: 50708
      dng_tags[3] = string("\xc6\x20", 2);                // tag: 50720
      dng_tags[4] =
          string("\xc6\x2d\x00\x04\x00\x00\x00\x01", 8);  // tag: 50733
    } else {
      dng_tags[0] =
          string("\x12\xc6\x01\x00\x04\x00\x00\x00", 8);  // tag: 50706
      dng_tags[1] =
          string("\x13\xc6\x01\x00\x04\x00\x00\x00", 8);  // tag: 50707
      dng_tags[2] = string("\x14\xc6\x02\x00", 4);        // tag: 50708
      dng_tags[3] = string("\x20\xc6", 2);                // tag: 50720
      dng_tags[4] =
          string("\x2d\xc6\x04\x00\x01\x00\x00\x00", 8);  // tag: 50733
    }
    int tags_found = 0;
    for (int i = 0; i < kTagsCount; ++i) {
      if (IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                           dng_tags[i], NULL)) {
        tags_found++;
      }
    }
    return tags_found >= 2;
  }
};

// Kodak RAW.
class KdcTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kKdcImage; }

  virtual size_t RequestedSize() const { return 5000; }

  // Check two points:
  // 1. valid endianness at the beginning of the file;
  // 2. two tags (WhiteBalance and SerialNumber) in the first requested bytes.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    // Search in first requested bytes
    const size_t kIfdTagsSize = 2;
    string kIfdTags[kIfdTagsSize];
    if (use_big_endian) {
      kIfdTags[0] = string("\xfa\x0d\x00\x01", 4);  // WhiteBalance
      kIfdTags[1] = string("\xfa\x00\x00\x02", 4);  // SerialNumber
    } else {
      kIfdTags[0] = string("\x0d\xfa\x01\x00", 4);  // WhiteBalance
      kIfdTags[1] = string("\x00\xfa\x02\x00", 4);  // SerialNumber
    }

    return IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kIfdTags[0], NULL) &&
           IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kIfdTags[1], NULL);
  }
};

// Leaf RAW.
class MosTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kMosImage; }

  virtual size_t RequestedSize() const { return 5000; }

  // Check two points:
  // 1. valid endianness at the beginning of the file;
  // 2. signature "PKTS    " in the first requested bytes. Note the
  // "whitespace". It's important as they are special binary values.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(source, &use_big_endian)) {
      return false;
    }

    // Search kSignaturePKTS in first requested bytes
    const string kSignaturePKTS("PKTS\x00\x00\x00\x001", 8);
    return IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kSignaturePKTS, NULL);
  }
};

// Minolta RAW.
class MrwTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kMrwImage; }

  virtual size_t RequestedSize() const { return 4; }

  // Check only the signature at the beginning of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    // Limits the source length to the RequestedSize(), using it guarantees that
    // we will not read more than this size from the source.
    RangeCheckedBytePtr limited_source =
        source.pointerToSubArray(0 /* pos */, RequestedSize());

    const string kSignature("\0MRM", 4);
    return IsSignatureMatched(limited_source, 0 /* offset */, kSignature);
  }
};

// Check if the file contains a NRW signature "NRW   " in the first requested
// bytes. Note the "whitespace". It's important as they are special binary
// values.
const size_t kRequestedSizeForNrwSignature = 4000;
bool ContainsNrwSignature(const RangeCheckedBytePtr& source) {
  // Search for kSignatureNrw.
  const string kSignatureNrw("NRW\x20\x20\x20", 6);
  return IsSignatureFound(source, 0 /* offset */, kRequestedSizeForNrwSignature,
                          kSignatureNrw, NULL);
}

// Checks if the file contains the signatures for Nikon formats:
// * the general Nikon singature "NIKON" string.
// * the ReferenceBlackWhite tag.
const size_t kRequestedSizeForNikonSignatures = 4000;
bool ContainsNikonSignatures(const RangeCheckedBytePtr& source,
                             const bool use_big_endian) {
  const string kSignatureNikon("NIKON");
  const string kReferenceBlackWhiteTag = use_big_endian
                                             ? string("\x02\x14\x00\x05", 4)
                                             : string("\x14\x02\x05\x00", 4);
  const std::vector<string> kSignatures = {kSignatureNikon,
                                           kReferenceBlackWhiteTag};
  for (auto const& signature : kSignatures) {
    if (!IsSignatureFound(source, 0, kRequestedSizeForNikonSignatures,
                          signature, NULL)) {
      return false;
    }
  }
  return true;
}

// Nikon RAW (NEF extension).
class NefTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kNefImage; }

  virtual size_t RequestedSize() const {
    return std::max(kRequestedSizeForNikonSignatures,
                    kRequestedSizeForNrwSignature);
  }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. magic number at the (offset == 2) position of the file;
  // 3. the signature "NIKON" in the requested bytes of the file;
  // 4. the ReferenceBlackWhite tag in the requested bytes of the file;
  // 5. does not contain the NRW signature. We may also check a special
  // signature "RAW   " similar to the NRW case, but we got issues in some
  // special images that the signature locates in the middle of the file, and it
  // costs too  long time to check;
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTiffMagic = 0x2A;  // NOLINT
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTiffMagic)) {
      return false;
    }

    return ContainsNikonSignatures(limited_source, use_big_endian) &&
           !ContainsNrwSignature(limited_source);  // not NRW
  }
};

// Nikon RAW (NRW extension).
class NrwTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kNrwImage; }

  virtual size_t RequestedSize() const {
    return std::max(kRequestedSizeForNikonSignatures,
                    kRequestedSizeForNrwSignature);
  }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. magic numbers at the (offset == 2 and offset == 4) positions of the
  // file;
  // 3. the signature "NIKON" in the first requested bytes of the file;
  // 4. the ReferenceBlackWhite tag in the requested bytes of the file;
  // 5. contains the NRW signature;
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTiffMagic = 0x2A;  // NOLINT
    const unsigned int kTiffOffset = 8;
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTiffMagic) ||
        !CheckUInt32Value(limited_source, 4 /* offset */, use_big_endian,
                          kTiffOffset)) {
      return false;
    }

    return ContainsNikonSignatures(limited_source, use_big_endian) &&
           ContainsNrwSignature(limited_source);
  }
};

// Olympus RAW.
class OrfTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kOrfImage; }

  virtual size_t RequestedSize() const { return 3000; }

  // Check multiple points:
  // 1. valid endianness at the beginning of the file;
  // 2. tag at the (offset == 2) position of the file;
  // 3. signature "OLYMP" in the first requested bytes.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const size_t kTagSize = 2;
    const unsigned short kTag[kTagSize] = {0x4F52, 0x5352};  // NOLINT
    if (!(CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                           kTag[0]) ||
          CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                           kTag[1]))) {
      return false;
    }

    // Search for kSignatureOlymp in first requested bytes
    const string kSignatureOlymp("OLYMP");
    return IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kSignatureOlymp, NULL);
  }
};

// Pentax RAW.
class PefTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kPefImage; }

  virtual size_t RequestedSize() const { return 1280; }

  // Check multiple points:
  // 1. valid big endianness at the beginning of the file;
  // 2. magic numbers at the (offset == 2 and offset==4) positions of the file;
  // 3. signature "AOC   " or "PENTAX  " in first requested bytes.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(limited_source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTiffMagic = 0x2A;  // NOLINT
    const unsigned int kTiffOffset = 8;
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTiffMagic) ||
        !CheckUInt32Value(limited_source, 4 /* offset */, use_big_endian,
                          kTiffOffset)) {
      return false;
    }

    // Search for kSignatureAOC or kSignaturePENTAX in first requested bytes
    const string kSignatureAOC("\x41\x4f\x43\x00\x4d\x4d", 6);
    const string kSignaturePENTAX("\x50\x45\x4e\x54\x41\x58\x20\x00", 8);
    return IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kSignatureAOC, NULL) ||
           IsSignatureFound(limited_source, 0 /* offset */, RequestedSize(),
                            kSignaturePENTAX, NULL);
  }
};

// Apple format.
class QtkTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kQtkImage; }

  virtual size_t RequestedSize() const { return 8; }

  // Check only the signature at the beginning of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    const size_t kSignatureSize = 2;
    const string kSignature[kSignatureSize] = {
        string("qktk\x00\x00\x00\x08", 8), string("qktn\x00\x00\x00\x08", 8),
    };
    return IsSignatureMatched(limited_source, 0 /* offset */, kSignature[0]) ||
           IsSignatureMatched(limited_source, 0 /* offset */, kSignature[1]);
  }
};

// Fuji RAW.
class RafTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kRafImage; }

  virtual size_t RequestedSize() const { return 8; }

  // Check only the signature at the beginning of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    const string kSignature("FUJIFILM");
    return IsSignatureMatched(limited_source, 0 /* offset */, kSignature);
  }
};

// Contax N RAW.
class RawContaxNTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kRawContaxNImage; }

  virtual size_t RequestedSize() const { return 36; }

  // Check only the signature at the (offset == 25) position of the
  // file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    const string kSignature("ARECOYK");
    return IsSignatureMatched(limited_source, 25, kSignature);
  }
};

// Panasonic RAW.
class Rw2TypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kRw2Image; }

  virtual size_t RequestedSize() const { return 4; }

  // Check two points: 1. valid endianness at the beginning of the
  // file; 2. tag at the (offset == 2) position of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTag = 0x55;  // NOLINT
    return CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                            kTag);
  }
};

// Samsung RAW.
class SrwTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kSrwImage; }

  virtual size_t RequestedSize() const { return 256; }

  // Check multiple points:
  // 1. valid big endianness at the beginning of the file;
  // 2. magic numbers at the (offset == 2 and offset==4) positions of the file;
  // 3. the signature "SAMSUNG" in the requested bytes of the file;
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    bool use_big_endian;
    if (!DetermineEndianness(source, &use_big_endian)) {
      return false;
    }

    const unsigned short kTiffMagic = 0x2A;  // NOLINT
    const unsigned int kTiffOffset = 8;
    if (!CheckUInt16Value(limited_source, 2 /* offset */, use_big_endian,
                          kTiffMagic) ||
        !CheckUInt32Value(limited_source, 4 /* offset */, use_big_endian,
                          kTiffOffset)) {
      return false;
    }

    const string kSignature("SAMSUNG");
    if (!IsSignatureFound(source, 0, RequestedSize(), kSignature, NULL)) {
      return false;
    }
    return true;
  }
};

// Sigma / Polaroid RAW.
class X3fTypeChecker : public TypeChecker {
 public:
  virtual RawImageTypes Type() const { return kX3fImage; }

  virtual size_t RequestedSize() const { return 4; }

  // Check only the signature at the beginning of the file.
  virtual bool IsMyType(const RangeCheckedBytePtr& source) const {
    RangeCheckedBytePtr limited_source = LimitSource(source);

    const string kSignature("FOVb", 4);
    return IsSignatureMatched(limited_source, 0 /* offset */, kSignature);
  }
};

// This class contains the list of all type checkers. One should used this list
// as a whole to execute the image type recognition.
class TypeCheckerList {
 public:
  TypeCheckerList() {
    // Add all supported RAW type checkers here.
    checkers_.push_back(new ArwTypeChecker());
    checkers_.push_back(new Cr3TypeChecker());
    checkers_.push_back(new Cr2TypeChecker());
    checkers_.push_back(new CrwTypeChecker());
    checkers_.push_back(new DcrTypeChecker());
    checkers_.push_back(new DngTypeChecker());
    checkers_.push_back(new KdcTypeChecker());
    checkers_.push_back(new MosTypeChecker());
    checkers_.push_back(new MrwTypeChecker());
    checkers_.push_back(new NefTypeChecker());
    checkers_.push_back(new NrwTypeChecker());
    checkers_.push_back(new OrfTypeChecker());
    checkers_.push_back(new PefTypeChecker());
    checkers_.push_back(new QtkTypeChecker());
    checkers_.push_back(new RafTypeChecker());
    checkers_.push_back(new RawContaxNTypeChecker());
    checkers_.push_back(new Rw2TypeChecker());
    checkers_.push_back(new SrwTypeChecker());
    checkers_.push_back(new X3fTypeChecker());

    // Sort the checkers by the ascending RequestedSize() to get better
    // performance when checking type.
    std::sort(checkers_.begin(), checkers_.end(), TypeChecker::Compare);
  }

  ~TypeCheckerList() {
    for (size_t i = 0; i < checkers_.size(); ++i) {
      delete checkers_[i];
      checkers_[i] = NULL;
    }
  }

  // Returns the type of source data. If it can not be identified, returns
  // kNonRawImage.
  RawImageTypes GetType(const RangeCheckedBytePtr& source) const {
    for (size_t i = 0; i < checkers_.size(); ++i) {
      if (checkers_[i]->IsMyType(source)) {
        return checkers_[i]->Type();
      }
    }
    return kNonRawImage;
  }

  // Returns the maximum size of requested size of data for identifying image
  // type using this class. The class guarantees that it will not read more than
  // this size.
  size_t RequestedSize() const {
    assert(!checkers_.empty());
    // The checkers_ is ascending sorted. The last element is the maximum.
    return checkers_.back()->RequestedSize();
  }

  bool IsOfType(const RangeCheckedBytePtr& source, const RawImageTypes type) {
    const TypeChecker* type_checker = GetTypeCheckerForType(type);
    if (type_checker) {
      return type_checker->IsMyType(source);
    } else {
      return false;
    }
  }

  size_t RequestedSizeForType(const RawImageTypes type) {
    const TypeChecker* type_checker = GetTypeCheckerForType(type);
    if (type_checker) {
      return type_checker->RequestedSize();
    } else {
      return 0;
    }
  }

 private:
  const TypeChecker* GetTypeCheckerForType(const RawImageTypes type) {
    for (const auto* type_checker : checkers_) {
      if (type_checker->Type() == type) {
        return type_checker;
      }
    }
    return nullptr;
  }

  std::vector<TypeChecker*> checkers_;
};

}  // namespace

bool IsRaw(const RawImageTypes type) {
  switch (type) {
    // Non-RAW-image type
    case kNonRawImage: {
      return false;
    }

    // Raw image types
    case kArwImage:
    case kCr3Image:
    case kCr2Image:
    case kCrwImage:
    case kDcrImage:
    case kDngImage:
    case kKdcImage:
    case kMosImage:
    case kMrwImage:
    case kNefImage:
    case kNrwImage:
    case kOrfImage:
    case kPefImage:
    case kQtkImage:
    case kRafImage:
    case kRawContaxNImage:
    case kRw2Image:
    case kSrwImage:
    case kX3fImage: {
      return true;
    }

    default: {
      // Unsupported type!
      assert(false);
    }
  }
  return false;
}

bool IsOfType(const RangeCheckedBytePtr& source, const RawImageTypes type) {
  return TypeCheckerList().IsOfType(source, type);
}

RawImageTypes RecognizeRawImageTypeLite(const RangeCheckedBytePtr& source) {
  return TypeCheckerList().GetType(source);
}

size_t GetNumberOfBytesForIsRawLite() {
  return TypeCheckerList().RequestedSize();
}

size_t GetNumberOfBytesForIsOfType(const RawImageTypes type) {
  return TypeCheckerList().RequestedSizeForType(type);
}

bool IsRawLite(const RangeCheckedBytePtr& source) {
  return IsRaw(RecognizeRawImageTypeLite(source));
}

}  // namespace image_type_recognition
}  // namespace piex