add the add_file_magic() class method
authorTony Cook <tony@develop-help.com>
Thu, 7 Feb 2019 11:27:15 +0000 (22:27 +1100)
committerTony Cook <tony@develop-help.com>
Thu, 7 Feb 2019 11:27:15 +0000 (22:27 +1100)
For now it's simple, I might extend it at some point to allow
for perl code or regexps for matching

Imager.pm
Imager.xs
context.c
image.c
imager.h
imageri.h
lib/Imager/Files.pod
t/200-file/100-files.t

index 5295780..e8fd6a4 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1487,6 +1487,27 @@ sub _get_writer_io {
   return ($io, @extras);
 }
 
   return ($io, @extras);
 }
 
+sub _test_format {
+  my ($io) = @_;
+
+  return i_test_format_probe($io, -1);
+}
+
+sub add_file_magic {
+  my ($class, %opts) = @_;
+
+  my $name = delete $opts{name};
+  my $bits = delete $opts{bits};
+  my $mask = delete $opts{mask};
+
+  unless (i_add_file_magic($name, $bits, $mask)) {
+    Imager->_set_error(Imager->_error_as_msg);
+    return;
+  }
+
+  1;
+}
+
 # Read an image from file
 
 sub read {
 # Read an image from file
 
 sub read {
@@ -1504,7 +1525,7 @@ sub read {
 
   my $type = $input{'type'};
   unless ($type) {
 
   my $type = $input{'type'};
   unless ($type) {
-    $type = i_test_format_probe($IO, -1);
+    $type = _test_format($IO);
   }
 
   if ($input{file} && !$type) {
   }
 
   if ($input{file} && !$type) {
@@ -2026,7 +2047,7 @@ sub read_multi {
 
   my $type = $opts{'type'};
   unless ($type) {
 
   my $type = $opts{'type'};
   unless ($type) {
-    $type = i_test_format_probe($IO, -1);
+    $type = _test_format($IO);
   }
 
   if ($opts{file} && !$type) {
   }
 
   if ($opts{file} && !$type) {
@@ -4748,9 +4769,13 @@ Where to find information on methods for Imager class objects.
 addcolors() - L<Imager::ImageTypes/addcolors()> - add colors to a
 paletted image
 
 addcolors() - L<Imager::ImageTypes/addcolors()> - add colors to a
 paletted image
 
+add_file_magic() - 
+
 addtag() -  L<Imager::ImageTypes/addtag()> - add image tags
 
 addtag() -  L<Imager::ImageTypes/addtag()> - add image tags
 
-add_type_extensions() -
+add_type_extensions() - L<Imager::Files/add_file_magic()> - add magic
+for new image file types.
+
 L<Imager::Files/add_type_extensions($type, $ext, ...)> - add extensions for
 new image file types.
 
 L<Imager::Files/add_type_extensions($type, $ext, ...)> - add extensions for
 new image file types.
 
index 58d92aa..c20b418 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2607,6 +2607,40 @@ i_test_format_probe(ig, length)
         Imager::IO     ig
               int     length
 
         Imager::IO     ig
               int     length
 
+int
+i_add_file_magic(name, bits_sv, mask_sv)
+        const char *name
+        SV *bits_sv
+        SV *mask_sv
+      PREINIT:
+        const unsigned char *bits;
+        const unsigned char *mask;
+       size_t bits_size;
+       size_t mask_size;
+      CODE:
+       i_clear_error();
+       bits = SvPV(bits_sv, bits_size);
+        mask = SvPV(mask_sv, mask_size);
+        if (bits_size == 0) {
+           i_push_error(0, "bits must be non-empty");
+           XSRETURN_EMPTY;
+       }
+       if (mask_size == 0) {
+           i_push_error(0, "mask must be non-empty");
+           XSRETURN_EMPTY;
+       }
+       if (bits_size != mask_size) {
+           i_push_error(0, "bits and mask must be the same length");
+           XSRETURN_EMPTY;
+       }
+       if (!*name) {
+           i_push_error(0, "name must be non-empty");
+           XSRETURN_EMPTY;
+       }
+       RETVAL = i_add_file_magic(name, bits, mask, bits_size);
+      OUTPUT:
+       RETVAL
+
 Imager::ImgRaw
 i_readpnm_wiol(ig, allow_incomplete)
         Imager::IO     ig
 Imager::ImgRaw
 i_readpnm_wiol(ig, allow_incomplete)
         Imager::IO     ig
index 8baa60e..99f7168 100644 (file)
--- a/context.c
+++ b/context.c
@@ -45,6 +45,8 @@ im_context_new(void) {
     return NULL;
   }
 
     return NULL;
   }
 
+  ctx->file_magic = NULL;
+
   ctx->refcount = 1;
 
 #ifdef IMAGER_TRACE_CONTEXT
   ctx->refcount = 1;
 
 #ifdef IMAGER_TRACE_CONTEXT
@@ -119,6 +121,18 @@ im_context_refdec(im_context_t ctx, const char *where) {
     if (ctx->error_stack[i].msg)
       myfree(ctx->error_stack[i].msg);
   }
     if (ctx->error_stack[i].msg)
       myfree(ctx->error_stack[i].msg);
   }
+
+  {
+    im_file_magic *p = ctx->file_magic;
+    while (p != NULL) {
+      im_file_magic *n = p->next;
+      free(p->m.name);
+      free(p->m.magic);
+      free(p->m.mask);
+      free(p);
+      p = n;
+    }
+  }
 #ifdef IMAGER_LOG
   if (ctx->lg_file && ctx->own_log)
     fclose(ctx->lg_file);
 #ifdef IMAGER_LOG
   if (ctx->lg_file && ctx->own_log)
     fclose(ctx->lg_file);
@@ -193,6 +207,38 @@ im_context_clone(im_context_t ctx, const char *where) {
 
   nctx->refcount = 1;
 
 
   nctx->refcount = 1;
 
+  {
+    im_file_magic *inp = ctx->file_magic;
+    im_file_magic **outpp = &nctx->file_magic;
+    *outpp = NULL;
+    while (inp) {
+      im_file_magic *m = malloc(sizeof(im_file_magic));
+      if (!m) {
+       /* should free any parts of the list already allocated */
+       im_context_refdec(nctx, "failed cloning");
+       return NULL;
+      }
+      m->next = NULL;
+      m->m.name = strdup(inp->m.name);
+      m->m.magic_size = inp->m.magic_size;
+      m->m.magic = malloc(inp->m.magic_size);
+      m->m.mask = malloc(inp->m.magic_size);
+      if (m->m.name == NULL || m->m.magic == NULL || m->m.mask == NULL) {
+       free(m->m.name);
+       free(m->m.magic);
+       free(m->m.mask);
+       free(m);
+       im_context_refdec(nctx, "failed cloning");
+       return NULL;
+      }
+      memcpy(m->m.magic, inp->m.magic, m->m.magic_size);
+      memcpy(m->m.mask, inp->m.mask, m->m.magic_size);
+      *outpp = m;
+      outpp = &m->next;
+      inp = inp->next;
+    }
+  }
+
 #ifdef IMAGER_TRACE_CONTEXT
   fprintf(stderr, "im_context:%s: cloned %p to %p\n", where, ctx, nctx);
 #endif
 #ifdef IMAGER_TRACE_CONTEXT
   fprintf(stderr, "im_context:%s: cloned %p to %p\n", where, ctx, nctx);
 #endif
@@ -297,3 +343,43 @@ im_context_slot_get(im_context_t ctx, im_slot_t slot) {
 
   return ctx->slots[slot];
 }
 
   return ctx->slots[slot];
 }
+
+/*
+=item im_add_file_magic(ctx, name, bits, mask, length)
+
+Add file type magic to the given context.
+
+=cut
+*/
+
+int
+im_add_file_magic(im_context_t ctx, const char *name,
+                 const unsigned char *bits, const unsigned char *mask,
+                 size_t length) {
+  im_file_magic *m = malloc(sizeof(im_file_magic));
+
+  if (m == NULL)
+    return 0;
+
+  if (length > 512)
+    length = 512;
+
+  m->m.name = strdup(name);
+  m->m.magic = malloc(length);
+  m->m.mask = malloc(length);
+  m->m.magic_size = length;
+
+  if (name == NULL || bits == NULL || mask == NULL) {
+    free(m->m.name);
+    free(m->m.magic);
+    free(m->m.mask);
+    free(m);
+    return 0;
+  }
+  memcpy(m->m.magic, bits, length);
+  memcpy(m->m.mask, mask, length);
+  m->next = ctx->file_magic;
+  ctx->file_magic = m;
+
+  return 1;
+}
diff --git a/image.c b/image.c
index 1af1eaa..7da5eca 100644 (file)
--- a/image.c
+++ b/image.c
@@ -1600,15 +1600,8 @@ i_gsamp_bits_fb(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, unsigned *samp
   }
 }
 
   }
 }
 
-struct magic_entry {
-  unsigned char *magic;
-  size_t magic_size;
-  char *name;
-  unsigned char *mask;  
-};
-
 static int
 static int
-test_magic(unsigned char *buffer, size_t length, struct magic_entry const *magic) {
+test_magic(unsigned char *buffer, size_t length, struct file_magic_entry const *magic) {
   if (length < magic->magic_size)
     return 0;
   if (magic->mask) {
   if (length < magic->magic_size)
     return 0;
   if (magic->mask) {
@@ -1646,8 +1639,8 @@ Check the beginning of the supplied file for a 'magic number'
   { (unsigned char *)(magic ""), sizeof(magic)-1, type, (unsigned char *)(mask) }
 
 const char *
   { (unsigned char *)(magic ""), sizeof(magic)-1, type, (unsigned char *)(mask) }
 
 const char *
-i_test_format_probe(io_glue *data, int length) {
-  static const struct magic_entry formats[] = {
+im_test_format_probe(im_context_t ctx, io_glue *data, int length) {
+  static const struct file_magic_entry formats[] = {
     FORMAT_ENTRY("\xFF\xD8", "jpeg"),
     FORMAT_ENTRY("GIF87a", "gif"),
     FORMAT_ENTRY("GIF89a", "gif"),
     FORMAT_ENTRY("\xFF\xD8", "jpeg"),
     FORMAT_ENTRY("GIF87a", "gif"),
     FORMAT_ENTRY("GIF89a", "gif"),
@@ -1714,7 +1707,7 @@ i_test_format_probe(io_glue *data, int length) {
     /* FLIF - Free Lossless Image Format - https://flif.info/spec.html */
     FORMAT_ENTRY("FLIF", "flif")
   };
     /* FLIF - Free Lossless Image Format - https://flif.info/spec.html */
     FORMAT_ENTRY("FLIF", "flif")
   };
-  static const struct magic_entry more_formats[] = {
+  static const struct file_magic_entry more_formats[] = {
     /* these were originally both listed as ico, but cur files can
        include hotspot information */
     FORMAT_ENTRY("\x00\x00\x01\x00", "ico"), /* Windows icon */
     /* these were originally both listed as ico, but cur files can
        include hotspot information */
     FORMAT_ENTRY("\x00\x00\x01\x00", "ico"), /* Windows icon */
@@ -1739,8 +1732,18 @@ i_test_format_probe(io_glue *data, int length) {
   }
 #endif
 
   }
 #endif
 
+  {
+    im_file_magic *p = ctx->file_magic;
+    while (p) {
+      if (test_magic(head, rc, &p->m)) {
+       return p->m.name;
+      }
+      p = p->next;
+    }
+  }
+
   for(i=0; i<sizeof(formats)/sizeof(formats[0]); i++) { 
   for(i=0; i<sizeof(formats)/sizeof(formats[0]); i++) { 
-    struct magic_entry const *entry = formats + i;
+    struct file_magic_entry const *entry = formats + i;
 
     if (test_magic(head, rc, entry)) 
       return entry->name;
 
     if (test_magic(head, rc, entry)) 
       return entry->name;
@@ -1751,7 +1754,7 @@ i_test_format_probe(io_glue *data, int length) {
     return "tga";
 
   for(i=0; i<sizeof(more_formats)/sizeof(more_formats[0]); i++) { 
     return "tga";
 
   for(i=0; i<sizeof(more_formats)/sizeof(more_formats[0]); i++) { 
-    struct magic_entry const *entry = more_formats + i;
+    struct file_magic_entry const *entry = more_formats + i;
 
     if (test_magic(head, rc, entry)) 
       return entry->name;
 
     if (test_magic(head, rc, entry)) 
       return entry->name;
index f5bb2da..f87fc2c 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -229,8 +229,15 @@ extern int i_img_is_monochrome(i_img *im, int *zero_is_white);
 extern int i_get_file_background(i_img *im, i_color *bg);
 extern int i_get_file_backgroundf(i_img *im, i_fcolor *bg);
 
 extern int i_get_file_background(i_img *im, i_color *bg);
 extern int i_get_file_backgroundf(i_img *im, i_fcolor *bg);
 
-const char * i_test_format_probe(io_glue *data, int length);
-
+const char * im_test_format_probe(im_context_t ctx, io_glue *data, int length);
+#define i_test_format_probe(io, length) im_test_format_probe(aIMCTX, (io), (length))
+
+/* file type magic to extend file detection */
+extern int im_add_file_magic(im_context_t ctx, const char *name,
+                            const unsigned char *bits, const unsigned char *mask,
+                            size_t length);
+#define i_add_file_magic(name, bits, mask, length) \
+  im_add_file_magic(aIMCTX, (name), (bits), (mask), (length))
 
 i_img   * i_readraw_wiol(io_glue *ig, i_img_dim x, i_img_dim y, int datachannels, int storechannels, int intrl);
 undef_int i_writeraw_wiol(i_img* im, io_glue *ig);
 
 i_img   * i_readraw_wiol(io_glue *ig, i_img_dim x, i_img_dim y, int datachannels, int storechannels, int intrl);
 undef_int i_writeraw_wiol(i_img* im, io_glue *ig);
index 14d1d23..1b5e4a8 100644 (file)
--- a/imageri.h
+++ b/imageri.h
@@ -112,6 +112,22 @@ i_img_dim i_abs(i_img_dim x);
 
 #define color_to_grey(col) ((col)->rgb.r * 0.222  + (col)->rgb.g * 0.707 + (col)->rgb.b * 0.071)
 
 
 #define color_to_grey(col) ((col)->rgb.r * 0.222  + (col)->rgb.g * 0.707 + (col)->rgb.b * 0.071)
 
+struct file_magic_entry {
+  unsigned char *magic;
+  size_t magic_size;
+  char *name;
+  unsigned char *mask;  
+};
+
+
+typedef struct im_file_magic im_file_magic;
+struct im_file_magic {
+  struct file_magic_entry m;
+
+  /* more magic to check */
+  im_file_magic *next;
+};
+
 #define IM_ERROR_COUNT 20
 typedef struct im_context_tag {
   int error_sp;
 #define IM_ERROR_COUNT 20
 typedef struct im_context_tag {
   int error_sp;
@@ -138,6 +154,9 @@ typedef struct im_context_tag {
   size_t slot_alloc;
   void **slots;
 
   size_t slot_alloc;
   void **slots;
 
+  /* registered file type magic */
+  im_file_magic *file_magic;
+
   ptrdiff_t refcount;
 } im_context_struct;
 
   ptrdiff_t refcount;
 } im_context_struct;
 
index 4d94dd0..4fed5cf 100644 (file)
@@ -55,6 +55,8 @@ Imager::Files - working with image files
   my $img = Imager->new(file => $filename)
     or die Imager->errstr;
 
   my $img = Imager->new(file => $filename)
     or die Imager->errstr;
 
+  Imager->add_file_magic(name => $name, bits => $bits, mask => $mask);
+
 =head1 DESCRIPTION
 
 You can read and write a variety of images formats, assuming you have
 =head1 DESCRIPTION
 
 You can read and write a variety of images formats, assuming you have
@@ -425,6 +427,48 @@ C<RLE>.
 
 =back
 
 
 =back
 
+You can now add to the magic database Imager uses for detecting file
+types:
+
+=over
+
+=item add_file_magic()
+
+  Imager->add_file_magic(name => $name, bits => $bits, mask => $mask)
+
+Adds to list of magic, the parameters are all required.  The
+parameters are:
+
+=over
+
+=item *
+
+C<name> - the file type name to return on match.
+
+=item *
+
+C<bits> - a binary string to match.
+
+=item *
+
+C<mask> - a mask controlling which parts of I<bits> are significant.
+
+=back
+
+While I<mask> is mostly a bit mask, some byte values are translated,
+the space character is treated as all zeros (C<"\x00">), and the C<x> character as
+all ones (C<"\xFF">).
+
+New magic entries take priority over old entries.
+
+You can add more than one magic entry for a given I<name>.
+
+  Imager->add_file_magic(name => "heif",
+                         bits => "    ftypheif"
+                         mask => "    xxxxxxxx");
+
+=back
+
 =head2 Limiting the sizes of images you read
 
 =over
 =head2 Limiting the sizes of images you read
 
 =over
index 928dce3..c63414c 100644 (file)
@@ -294,6 +294,18 @@ E8 03 37 FF F7 D5 C2 D8 B7 D5 58 59 6E D9 71 8C
 CF 3A FF 56 5D FF 67 87 CE 9C 0E D6 69 CD 1F EF
 FLIF
 
 CF 3A FF 56 5D FF 67 87 CE 9C 0E D6 69 CD 1F EF
 FLIF
 
+ok(Imager->add_file_magic(name => 'testtype',
+                         bits => "    testtype",
+                         mask => "    xxxxxxxx"),
+   "add magic");
+
+probe_ok(<<TESTTYPE, "testtype", "Test adding a format with magic");
+46 4C 49 46 74 65 73 74 74 79 70 65 03 AF B0 B1
+E8 03 37 FF F7 D5 C2 D8 B7 D5 58 59 6E D9 71 8C
+0F A9 88 B4 1C B1 7F C0 2E FB 8C 7D 90 B6 04 DF
+CF 3A FF 56 5D FF 67 87 CE 9C 0E D6 69 CD 1F EF
+TESTTYPE
+
 { # RT 72475
   # check error messages from read/read_multi
   my $data = "nothing useful";
 { # RT 72475
   # check error messages from read/read_multi
   my $data = "nothing useful";
@@ -371,7 +383,7 @@ sub probe_ok {
   my $data = pack("H*", $packed);
 
   my $io = Imager::io_new_buffer($data);
   my $data = pack("H*", $packed);
 
   my $io = Imager::io_new_buffer($data);
-  my $result = Imager::i_test_format_probe($io, -1);
+  my $result = Imager::_test_format($io);
 
   return $builder->is_eq($result, $exp_type, $name)
 }
 
   return $builder->is_eq($result, $exp_type, $name)
 }