summaryrefslogtreecommitdiff
path: root/src/loader/detect.c
blob: 1806874b806dc497d406d53d68ff82c609ace6e9 (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
/*:*
 * ggaze — image format detection
 *
 * Magic-byte sniffing. Pure function, no I/O, no GTK -> unit-testable.
 *
 * Copyright (c) 2026 ggaze contributors
 * SPDX-License-Identifier: GPL-3.0-or-later
 *:*/

#include "detect.h"

#include <string.h>

GgazeFormat
detect_format(const guint8 *p_head, gsize u_len) {
   if (p_head == NULL || u_len == 0) {
      return (GGAZE_FMT_UNKNOWN);
   }

   /* JPEG: FF D8 FF */
   if (u_len >= 3 && p_head[0] == 0xFF && p_head[1] == 0xD8 &&
       p_head[2] == 0xFF) {
      return (GGAZE_FMT_JPEG);
   }

   /* PNG: 89 50 4E 47 0D 0A 1A 0A */
   if (u_len >= 8 && p_head[0] == 0x89 && p_head[1] == 'P' &&
       p_head[2] == 'N' && p_head[3] == 'G' && p_head[4] == 0x0D &&
       p_head[5] == 0x0A && p_head[6] == 0x1A && p_head[7] == 0x0A) {
      return (GGAZE_FMT_PNG);
   }

   /* GIF: "GIF8" */
   if (u_len >= 4 && p_head[0] == 'G' && p_head[1] == 'I' && p_head[2] == 'F' &&
       p_head[3] == '8') {
      return (GGAZE_FMT_GIF);
   }

   /* WebP: RIFF .... WEBP */
   if (u_len >= 12 && memcmp(p_head, "RIFF", 4) == 0 &&
       memcmp(p_head + 8, "WEBP", 4) == 0) {
      return (GGAZE_FMT_WEBP);
   }

   /* TIFF: II 2A 00 (little) | MM 00 2A (big) */
   if (u_len >= 4 && ((p_head[0] == 'I' && p_head[1] == 'I' &&
                       p_head[2] == 0x2A && p_head[3] == 0x00) ||
                      (p_head[0] == 'M' && p_head[1] == 'M' &&
                       p_head[2] == 0x00 && p_head[3] == 0x2A))) {
      return (GGAZE_FMT_TIFF);
   }

   /* ICO: 00 00 01 00 */
   if (u_len >= 4 && p_head[0] == 0x00 && p_head[1] == 0x00 &&
       p_head[2] == 0x01 && p_head[3] == 0x00) {
      return (GGAZE_FMT_ICO);
   }

   /* JPEG XL: codestream FF 0A, or container 00 00 00 0C "JXL " */
   if (u_len >= 2 && p_head[0] == 0xFF && p_head[1] == 0x0A) {
      return (GGAZE_FMT_JXL);
   }
   if (u_len >= 12 && p_head[0] == 0x00 && p_head[1] == 0x00 &&
       p_head[2] == 0x00 && p_head[3] == 0x0C &&
       memcmp(p_head + 4, "JXL ", 4) == 0) {
      return (GGAZE_FMT_JXL);
   }

   /* AVIF / HEIF: ISO BMFF ftyp box at offset 4; brand at offset 8. */
   if (u_len >= 12 && memcmp(p_head + 4, "ftyp", 4) == 0) {
      const guint8 *p_brand = p_head + 8;
      if (memcmp(p_brand, "avif", 4) == 0 || memcmp(p_brand, "avis", 4) == 0) {
         return (GGAZE_FMT_AVIF);
      }
      if (memcmp(p_brand, "heic", 4) == 0 || memcmp(p_brand, "heix", 4) == 0 ||
          memcmp(p_brand, "mif1", 4) == 0) {
         return (GGAZE_FMT_HEIF);
      }
   }

   return (GGAZE_FMT_UNKNOWN);
}