changeset 45:7cfa60beda76

Thu May 26 13:57:19 2005 John Ellis <johne@verizon.net> * format_raw.[ch]: Move camera specific code to manufacturer specific format_*.c files. Change code so that file descripter version is now a separate functions that wraps the standard parser by using mmap. * format_canon.[ch]: Moved Canon specific raw support here, removed file descriptor versions of parser. This Canon raw file parser written by Daniel M. German. * format_fuji.[ch]: Move Fuji specific raw support here, parser written by Lars Ellenberg. * exif.c: Update for change to format_raw_img_exif_offsets. * filelist.c: Add cr2 extension to Canon raw format list. * image-load.c: Fixes for changes to format_raw_img_exif_offset_fd so that buffer is refilled using new offset of file descriptor. * src/Makefile.am: Add format_canon.[ch], format_fuji.[ch] to build. ##### Note: GQview CVS on sourceforge is not always up to date, please use ##### ##### an offical release when making enhancements and translation updates. #####
author gqview
date Thu, 26 May 2005 18:10:52 +0000
parents 458e396d3f35
children 905f8fa583a3
files ChangeLog THIS_CVS_IS_NOT_UP_TO_DATE src/Makefile.am src/exif.c src/filelist.c src/format_canon.c src/format_canon.h src/format_fuji.c src/format_fuji.h src/format_raw.c src/format_raw.h src/image-load.c
diffstat 12 files changed, 737 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed May 18 23:52:16 2005 +0000
+++ b/ChangeLog	Thu May 26 18:10:52 2005 +0000
@@ -1,3 +1,19 @@
+Thu May 26 13:57:19 2005  John Ellis  <johne@verizon.net>
+
+	* format_raw.[ch]: Move camera specific code to manufacturer specific
+	format_*.c files. Change code so that file descripter version is now a
+	separate functions that wraps the standard parser by using mmap.
+	* format_canon.[ch]: Moved Canon specific raw support here, removed
+	file descriptor versions of parser. This Canon raw file parser written
+	by Daniel M. German.
+	* format_fuji.[ch]: Move Fuji specific raw support here, parser written
+	by Lars Ellenberg.
+	* exif.c: Update for change to format_raw_img_exif_offsets.
+	* filelist.c: Add cr2 extension to Canon raw format list.
+	* image-load.c: Fixes for changes to format_raw_img_exif_offset_fd so
+	that buffer is refilled using new offset of file descriptor.
+	* src/Makefile.am: Add format_canon.[ch], format_fuji.[ch] to build.
+
 Wed May 18 19:36:49 2005  John Ellis  <johne@verizon.net>
 
 	* utilops.[ch] (file_util_rename_dir): New utility to rename a folder,
--- a/THIS_CVS_IS_NOT_UP_TO_DATE	Wed May 18 23:52:16 2005 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-The CVS on sourceforge is far behind the latest version,
-this CVS is not used for GQview development.
-
-There is no public CVS!
-
-(This CVS was set up as a test, and is here in the event GQview development CVS does go public)
--- a/src/Makefile.am	Wed May 18 23:52:16 2005 +0000
+++ b/src/Makefile.am	Thu May 26 18:10:52 2005 +0000
@@ -84,6 +84,10 @@
 	exif.h		\
 	filelist.c	\
 	filelist.h	\
+	format_canon.c	\
+	format_canon.h	\
+	format_fuji.c	\
+	format_fuji.h	\
 	format_raw.c	\
 	format_raw.h	\
 	fullscreen.c	\
--- a/src/exif.c	Wed May 18 23:52:16 2005 +0000
+++ b/src/exif.c	Thu May 26 18:10:52 2005 +0000
@@ -1088,7 +1088,7 @@
 		{
 		guint32 offset = 0;
 		
-		if (format_raw_img_exif_offsets(-1, f, size, NULL, &offset))
+		if (format_raw_img_exif_offsets(f, size, NULL, &offset))
 			{
 			res = parse_TIFF(exif, (unsigned char*)f + offset, size - offset);
 			}
--- a/src/filelist.c	Wed May 18 23:52:16 2005 +0000
+++ b/src/filelist.c	Thu May 26 18:10:52 2005 +0000
@@ -211,7 +211,8 @@
 	/* These are the raw camera formats with embedded jpeg/exif.
 	 * (see format_raw.c)
 	 */
-	filter_add_if_missing("raf", "Fujifilm raw camera format", ".raf", TRUE);
+	filter_add_if_missing("raf", "Fujifilm raw format", ".raf", TRUE);
+	filter_add_if_missing("crw", "Canon raw format", ".crw;.cr2", TRUE);
 }
 
 static GList *filter_to_list(const gchar *extensions)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/format_canon.c	Thu May 26 18:10:52 2005 +0000
@@ -0,0 +1,473 @@
+/*
+ *  GQView
+ *  (C) 2005 John Ellis
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ *
+ *
+ * Code to add support for Canon CR2 and CRW files, version 0.2
+ *
+ * Developed by Daniel M. German, dmgerman at uvic.ca 
+ *
+ * you can find the sources for this patch at http://turingmachine.org/~dmg/libdcraw/gqview/
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "intl.h"
+
+#include "format_canon.h"
+#include "format_raw.h"
+
+
+#if 0
+  #define CANON_DEBUG
+#endif
+
+#ifdef CANON_DEBUG
+int canonEnableDebug = 0;
+/* This should be really a stack, but I am too lazy to implement */
+#define DEBUG_ENABLE (canonEnableDebug = 0)
+#define DEBUG_DISABLE (canonEnableDebug = 1)
+/* It would be nice if these functions indented according to depth in the stack, but I am too lazy to implement */
+
+#define DEBUG_ENTRY(a) (canonEnableDebug || fprintf(stderr, "Entering function: %s [%s:%d]\n", a, __FILE__, __LINE__))
+#define DEBUG_EXIT(a) (canonEnableDebug || fprintf(stderr, "Exiting function: %s [%s:%d]\n", a, __FILE__, __LINE__))
+#define DEBUG_1(a) (canonEnableDebug || fprintf(stderr, a " [%s:%d]\n", __FILE__, __LINE__))
+#define DEBUG_2(a,b) (canonEnableDebug || fprintf(stderr, a " [%s:%d]\n",b,  __FILE__, __LINE__))
+#define DEBUG_3(a,b,c) (canonEnableDebug || fprintf(stderr, a " [%s:%d]\n",b, c,  __FILE__, __LINE__))
+
+#else
+#define DEBUG_ENABLE
+#define DEBUG_DISABLE 
+#define DEBUG_ENTRY(a)
+#define DEBUG_EXIT(a)
+
+#define DEBUG_1(a) 
+#define DEBUG_2(a,b)
+#define DEBUG_3(a,b,c)
+#endif
+
+
+/* canon_read_int4 
+
+
+The problem with gqview is that sometimes the data is to be read from
+a file, and sometimes it is in memory. This function tries to isolate
+the rest of the code from having to deal with both cases
+
+This function reads a 4 byte unsigned integer, and fixes its endianism.
+
+If fd >= 0 then the value is read from the corresponding file descriptor
+   
+   in that case, if offset is > 0, then the value is read from that offset
+
+   otherwise it is read from the current file pointer 
+
+if fd < 0 then the value is read from the memory pointed by data + offset
+
+
+offset is a pointer to the actual offset of the file.
+
+sizeInt can be 2 or 4 (it is the number of bytes to read)
+
+RETURNS true is no error, false if it can't read the value
+
+
+*/
+static int canon_read_int(unsigned int *offset, const void *data, int sizeInt, unsigned int *value )
+{
+  DEBUG_DISABLE;
+
+  DEBUG_ENTRY("canon_read_int");
+  /* Verify values before we do anything */
+  if (sizeInt != 2 && sizeInt != 4) return FALSE;
+  if (offset == NULL) return FALSE;
+  if (*offset <= 0) return FALSE;
+  if (data == NULL) return FALSE;
+  if (value == NULL) return FALSE;
+
+  if (sizeInt == 4) {
+    *value = GUINT32_FROM_LE(*(guint32*)(data + *offset));      
+    *offset +=4;
+    DEBUG_3("Read 4 bytes %d %x", *value, *value);
+  } else {
+    *value = GUINT16_FROM_LE(*(guint32*)(data + *offset));
+    *offset +=2;
+    DEBUG_3("Read 2 bytes %d %x", *value, *value);
+  }
+
+  DEBUG_EXIT("canon_read_int");
+
+  DEBUG_ENABLE;
+  return TRUE;
+}
+
+#define CANON_HEADER_SIZE                   26
+
+/*
+
+ The CR2 format is really a TIFF format. It is nicely documented in the TIFF V 6.0 document available from adobe.
+
+  The CR2 file contains two thumbnails, one tiny and one decent sized. The record Id of the latter is 0x0111.
+
+  The photo info is also available, in EXIF, and it looks like I don't need to do anything! Yeah!
+
+*/
+
+static int canon_cr2_process_directory(void *data, int offsetIFD, guint *jpegLocation, guint *exifLocation) 
+{
+  unsigned int offset;
+  int returnValue = FALSE;
+
+  DEBUG_ENTRY("canon_cr2_process_directory");
+
+  /* The directory is a link list, after an array of records, the next 4 byptes point to the offset of the next directory.
+
+  All offsets are absolution within the file (in CRWs the offsets are relative ).
+
+  */
+
+  while (offsetIFD != 0 && offsetIFD != 0xFFFF) {
+    int countEntries=0;
+    int i;
+    /* Read directory, we start by reading number of entries in the directory */
+
+    offset = offsetIFD;
+    if (!canon_read_int(&offset, data, 2, &countEntries)) {
+      goto return_only;
+    }
+    DEBUG_2("Number of entries: %d\n", countEntries);
+
+    for (i=0;i<countEntries;i++) {
+      /* read each entry */
+
+      int recordId;
+#if 0
+      int format;
+      int size;
+#endif
+
+      /* read record type */
+      if (!canon_read_int(&offset, data, 2, &recordId)) {
+	goto return_only;
+      }
+
+      /* Did we find the JPEG */
+      if (recordId == 0x0111) { 
+	DEBUG_1("This is the record to find**********************\n");
+	offset +=6;
+	if (!canon_read_int(&offset, data, 4, jpegLocation)) {
+	  goto return_only;
+	}
+	DEBUG_3("JPEG Location %d 0x%x\n", *jpegLocation, *jpegLocation);
+	/* We don't want to keep reading, because there is another
+	   0x0111 record at the end that contains the raw data */
+	returnValue = TRUE;
+	goto return_only;
+      } else {
+	/* advance pointer by skipping rest of record */
+	offset += 10;
+      }
+    }
+    /* The next 4 bytes are the offset of next directory, if zero we are done
+       
+     */
+    if (!canon_read_int(&offset, data, 4, &offsetIFD)) {
+      goto return_only;
+    }
+    DEBUG_3("Value of NEXT offsetIFD: %d 0x%x\n", offsetIFD, offsetIFD);
+  }
+
+  returnValue = TRUE;
+  DEBUG_1("Going to return true");
+
+ return_only:
+  DEBUG_EXIT("canon_cr2_process_directory");
+
+  return TRUE;
+
+
+}
+
+
+static int format_raw_test_canon_cr2(void *data, const guint len,
+				     guint *image_offset, guint *exif_offset)
+{
+#if 0
+  char signature[4];
+  unsigned int offset = 4;
+#endif
+  int offsetIFD;
+  int returnValue = FALSE;
+  void *jpgInDataOffset;
+
+  DEBUG_ENTRY("format_raw_test_canon_cr2");
+
+  /* Verify signature */
+  if (memcmp(data, "\x49\x49\x2a\00", 4) != 0) {
+    DEBUG_1("This is not a CR2");
+    goto return_only;
+  }
+
+  /* Get address of first directory */
+  offsetIFD = GUINT32_FROM_LE(*(guint32*)(data + 4));
+
+
+  DEBUG_2("Value of offsetIFD: %d\n", offsetIFD);
+
+  returnValue = canon_cr2_process_directory(data, offsetIFD, image_offset, exif_offset);
+
+  if (returnValue) {
+    jpgInDataOffset = data + *image_offset;
+
+    /* Make sure we really got a JPEG */
+
+    if (memcmp(jpgInDataOffset, "\xff\xd8",2) != 0) {
+      /* It is not at the JPEG! */
+      DEBUG_2("THis is not a jpeg after all: there are the first 4 bytes 0x%x ", (int)jpgInDataOffset);
+      returnValue = FALSE;
+    }
+  }
+
+return_only:
+  DEBUG_EXIT("format_raw_test_canon_cr2");
+
+  return returnValue;
+}
+
+
+gint format_raw_test_canon(const void *data, const guint len,
+			   guint *image_offset, guint *exif_offset)
+{
+
+
+  /* There are at least 2 types of Canon raw files. CRW and CR2 
+
+  CRW files have a proprietary format. 
+
+  HEADER
+  Heap
+    RAW   data
+    JPEG  data
+    PHoto data
+
+  HEADER_LENGTH            32  bytes
+   int2     byteOrder; Always II (MM Motorola ---big endian, II Intel --little endian)
+   int4     length;    Should be 26 
+   char     identifier[8];type HEAP, subtype heap  CCDR
+   int2     version;
+   int2     subversion;
+   char     unused[14]; 
+  */
+
+  int returnValue = FALSE;
+  int heapHeaderOffset = 0;
+  int heapRecordsCount = 0;
+#if 0
+  guint32 rawInt4;
+  guint16 rawInt2;
+#endif
+  int i;
+  unsigned int currentOffset;
+  /* File has to be little endian, first two bytes II */
+
+  if (len < 100) 
+    return FALSE;
+
+  if (format_raw_test_canon_cr2((void *)data, len, image_offset, exif_offset)) {
+    return TRUE;
+  }
+
+  if (memcmp("II", data, 2) != 0) {
+    return FALSE;
+  }
+  /* NO DEBUG BEFORE THIS POINT, we want to debug only Canon */
+  
+  DEBUG_ENTRY("format_raw_test_canon");
+
+  DEBUG_2("Length of buffer read %u", len);
+
+  DEBUG_2("CRW header length Data %d", GUINT32_FROM_LE(*(guint32*)(data + 2)));
+
+  /* the length has to be CANON_HEADER_SIZE  */
+  if (GUINT32_FROM_LE(*(guint32*)(data + 2)) != CANON_HEADER_SIZE) {
+    DEBUG_1("It is not the right size");
+    goto return_only;
+  }
+  
+  if (!memcmp("HEAPCCDR", data+6, 8) == 0) {
+    DEBUG_1("This file is not a Canon CRW raw photo");
+    goto return_only;
+
+   }
+   
+  /* Ok, so now we know that this is a CRW file */
+
+  /* The heap is a strange data structure. It is recursive, so a record
+    can contain a heap itself. That is indeed the case for the photo information
+    reecord. Luckily the first heap contains the jpeg, so we don't need to do
+    any recursive processing. 
+
+    Its "header" is a the end. The header is a sequence of records,
+     and the data of each record is at the beginning of the heap
+
+   +-----------------+
+   | data raw        |
+   +-----------------+
+   | data jpeg       |
+   +-----------------+
+   | data photo info |
+   +-----------------+
+   |header of heap   |
+   | # records       |   it should be 3
+   |      raw info   |
+   |      jpeg info  |
+   |      photo info |
+   +-----------------+
+
+   The header contains 
+      number of records: 2 bytes
+      for each record (10 bytes long)
+          type:    2 bytes
+          length:  4 bytes 
+          offset:  4 bytes 
+	  
+     In some records the length and offset are actually data,
+     but none for the ones in the first heap.
+     
+     the offset is with respect to the beginning of the heap, not the
+     beginning of the file. That allows heaps to be "movable"
+
+   For the purpose of finding the JPEG, all we need is to scan the fist heap,
+   which contains the following record types:
+
+    0x2005 Record RAW data
+    0x2007 Record JPEG data
+    0x300a Record with photo info
+
+  */
+
+
+  if (len < 0x10000) {
+    DEBUG_2("We have a problem, the length is too small %d ", len);
+    goto return_only;
+  }
+  currentOffset = len-4;
+
+
+  /* The last 4 bytes have the offset of the header of the heap */
+  if (!canon_read_int(&currentOffset, data, 4, &heapHeaderOffset)) 
+    goto return_only;
+  
+  /* The heapoffset has to be adjusted to the actual file size, the header is CANON_HEADER_SIZE bytes long */
+  heapHeaderOffset += CANON_HEADER_SIZE;
+  DEBUG_2("heap header Offset %d ", heapHeaderOffset);
+  
+  /* Just check, it does not hurt, we don't want to crash */
+  if (heapHeaderOffset > len) 
+    goto return_only;
+
+  currentOffset =   heapHeaderOffset;
+  /* Let us read the number of records in the heap */
+  if (!canon_read_int(&currentOffset, data, 2, &heapRecordsCount))
+    goto return_only;
+  
+  DEBUG_2("heap record count %d ", heapRecordsCount);
+    
+  if (heapRecordsCount != 3) {
+    /* In all the cameras I have seen, this is always 3
+       if not, something is wrong, so just quit */
+    goto return_only;
+  }
+    
+  for (i=0;i<3;i++) {
+    int recordType;
+    int recordOffset;
+    int recordLength;
+    const void *jpgInDataOffset;
+    /* Read each record, to find jpg, it should be second */
+    
+    if (!canon_read_int(&currentOffset, data, 2, &recordType))
+      goto return_only;
+    
+    DEBUG_2("record type 0x%x ", recordType);
+    
+    if (recordType != 0x2007) {
+      /* Go to the next record, don't waste time, 
+	 but first, eat 8 bytes from header */
+      currentOffset += 8;
+      continue; /* Nah, wrong record, go to next */
+    }
+    /* Bingo, we are at the JPEG record */
+    
+    /* Read length */
+    if (!canon_read_int(&currentOffset, data, 4, &recordLength))
+      goto return_only;
+    
+    DEBUG_2("record length %d ", recordLength);
+    
+    /* Read offset */
+    
+    if (!canon_read_int(&currentOffset, data, 4, &recordOffset))
+      goto return_only;
+    
+    DEBUG_2("record offset 0x%d ", recordOffset);
+    
+    /* Great, we now know where the JPEG is! 
+       it is CANON_HEADER_SIZE (size of CRW header) + recordOffset 
+    */
+    
+    *image_offset =  CANON_HEADER_SIZE + recordOffset;
+    DEBUG_2("image offset %d ", *image_offset);
+    
+    /* keep checking for potential errors */
+    if (*image_offset > len) {
+      goto return_only;
+    }
+    /* Get the JPEG is */
+    
+    jpgInDataOffset = data + *image_offset;
+
+    if (memcmp(jpgInDataOffset, "\xff\xd8\xff\xdb",4) != 0) {
+      /* It is not at the JPEG! */
+      DEBUG_2("THis is not a jpeg after all: there are the first 4 bytes 0x%x ", (int)jpgInDataOffset);
+      goto return_only;
+    }
+    returnValue = TRUE;
+    goto return_only;
+  }
+ /* undo whatever we need in case of an error*/
+  DEBUG_1("We scan all records, but nothing was found!!!!!!!!!!!!!!!!!!");
+
+
+  /* At this point we are returning */
+return_only:
+  if (returnValue) {
+    DEBUG_1("****We got an embedded  JPEG for a canon CRW");
+
+  }
+
+  DEBUG_EXIT("format_raw_test_canon");
+  return returnValue;
+
+#undef DEBUG_2
+#undef DEBUG
+#undef DEBUG_ENTRY
+#undef DEBUG_EXIT
+
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/format_canon.h	Thu May 26 18:10:52 2005 +0000
@@ -0,0 +1,31 @@
+/*
+ *  GQView
+ *  (C) 2005 John Ellis
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ *
+ *
+ * Code to add support for Canon CR2 and CRW files, version 0.2
+ *
+ * Developed by Daniel M. German, dmgerman at uvic.ca 
+ *
+ * you can find the sources for this patch at http://turingmachine.org/~dmg/libdcraw/gqview/
+ *
+ */
+
+#ifndef __FORMAT_RAW_CANON_H
+#define __FORMAT_RAW_CANON_H
+
+
+gint format_raw_test_canon(const void *data, const guint len,
+			   guint *image_offset, guint *exif_offset);
+
+
+#define FORMAT_RAW_CANON { "II", 2, "Canon crw format", format_raw_test_canon }, \
+			 { "\x49\x49\x2a\00", 4, "Canon cr2 format", format_raw_test_canon }
+
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/format_fuji.c	Thu May 26 18:10:52 2005 +0000
@@ -0,0 +1,59 @@
+/*
+ *  GQView
+ *  (C) 2005 John Ellis
+ *
+ *  Authors:
+ *    Original version 2005 Lars Ellenberg, base on dcraw by David coffin.
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "intl.h"
+
+#include "format_fuji.h"
+#include "format_raw.h"
+
+
+gint format_raw_test_fuji(const void *data, const guint len,
+			  guint *image_offset, guint *exif_offset)
+{
+	guint io;
+	guint eo;
+
+	if (len < 128 ||
+	    memcmp(data, "FUJIFILM", 8) != 0)
+		{
+		return FALSE;
+		}
+
+	io = GUINT32_FROM_BE(*(guint32*)(data + 84));
+	eo = *image_offset + 12;
+
+	/* verify jpeg marker */
+	if (memcmp(data + io, "\xff\xd8\xff\xe1", 4) != 0)
+		{
+		return FALSE;
+		}
+
+	if (image_offset) *image_offset = io;
+	if (exif_offset) *exif_offset = eo;
+
+	printf("raw Fuji format file\n");
+
+	return TRUE;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/format_fuji.h	Thu May 26 18:10:52 2005 +0000
@@ -0,0 +1,25 @@
+/*
+ *  GQView
+ *  (C) 2005 John Ellis
+ *
+ *  Authors:
+ *    Original version 2005 Lars Ellenberg, base on dcraw by David coffin.
+ *
+ * This software is released under the GNU General Public License (GNU GPL).
+ * Please read the included file COPYING for more information.
+ * This software comes with no warranty of any kind, use at your own risk!
+ */
+
+#ifndef __FORMAT_RAW_FUJI_H
+#define __FORMAT_RAW_FUJI_H
+
+
+gint format_raw_test_fuji(const void *data, const guint len,
+			  guint *image_offset, guint *exif_offset);
+
+
+#define FORMAT_RAW_FUJI { "FUJIFILM", 8, "Fuji raw format", format_raw_test_fuji }
+
+
+#endif
+
--- a/src/format_raw.c	Wed May 18 23:52:16 2005 +0000
+++ b/src/format_raw.c	Thu May 26 18:10:52 2005 +0000
@@ -14,9 +14,13 @@
 #  include "config.h"
 #endif
 
+
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
 
 #include <glib.h>
 
@@ -24,54 +28,59 @@
 
 #include "format_raw.h"
 
-static gint format_raw_test_canon(int fd, const void *data, const guint len,
-				  guint *image_offset, guint *exif_offset)
-{
-	return FALSE;
-}
+#include "format_canon.h"
+#include "format_fuji.h"
+
+
+typedef struct _FormatEntry FormatEntry;
+struct _FormatEntry {
+	const void *header_pattern;
+	const guint header_length;
+	const gchar *description;
+	FormatRawParseFunc func_parse;
+};
+
 
-static gint format_raw_test_fuji(int fd, const void *data, const guint len,
-				  guint *image_offset, guint *exif_offset)
+static FormatEntry format_list[] = {
+	FORMAT_RAW_CANON,
+	FORMAT_RAW_FUJI,
+	{ NULL, 0, NULL, NULL }
+};
+
+
+static FormatEntry *format_raw_find(const void *data, const guint len)
 {
-	if (len < 128 ||
-	    memcmp(data, "FUJIFILM", 8) != 0)
+	gint n;
+
+	n = 0;
+	while (format_list[n].header_pattern)
 		{
-		return FALSE;
+		if (format_list[n].header_length <= len &&
+		    memcmp(data, format_list[n].header_pattern, format_list[n].header_length) == 0)
+			{
+			return &format_list[n];
+			}
+		n++;
 		}
 
-	*image_offset = GUINT32_FROM_BE(*(guint32*)(data + 84));
-	*exif_offset = *image_offset + 12;
-printf("found a raw fuji file!\n");
-	return TRUE;
+	return NULL;
 }
 
-static gint format_raw_test_nikon(int fd, const void *data, const guint len,
-				  guint *image_offset, guint *exif_offset)
+static gint format_raw_parse(FormatEntry *entry,
+			     const void *data, const guint len,
+			     guint *image_offset, guint *exif_offset)
 {
-	return FALSE;
-}
-
-
-gint format_raw_img_exif_offsets(int fd, const void *data, const guint len,
-				 guint *image_offset, guint *exif_offset)
-{
-	guint32 io = 0;
-	guint32 eo = 0;
+	gint io = 0;
+	gint eo = 0;
 	gint found;
 
-	if (fd < 0 && !data) return FALSE;
-#if 0
-	if (len < 512) return FALSE;
-#endif
+	if (!entry || !entry->func_parse) return FALSE;
 
-	found = format_raw_test_canon(fd, data, len, &io, &eo) ||
-		format_raw_test_fuji (fd, data, len, &io, &eo) ||
-		format_raw_test_nikon(fd, data, len, &io, &eo);
+	found = entry->func_parse(data, len, &io, &eo);
 
 	if (!found ||
 	    io >= len - 4 ||
-	    eo >= len ||
-	    memcmp(data + io, "\xff\xd8\xff\xe1", 4) != 0)	/* jpeg marker */
+	    eo >= len)
 		{
 		return FALSE;
 		}
@@ -82,5 +91,69 @@
 	return TRUE;
 }
 
+gint format_raw_img_exif_offsets(const void *data, const guint len,
+				 guint *image_offset, guint *exif_offset)
+{
+	FormatEntry *entry;
+
+	if (!data || len < 1) return FALSE;
+
+	entry = format_raw_find(data, len);
+
+	if (!entry || !entry->func_parse) return FALSE;
+
+	return format_raw_parse(entry, data, len, image_offset, exif_offset);
+}
 
 
+gint format_raw_img_exif_offsets_fd(int fd, const void *header_data, const guint header_len,
+				    guint *image_offset, guint *exif_offset)
+{
+	FormatEntry *entry;
+	void *map_data = NULL;
+	size_t map_len = 0;
+	struct stat st;
+	gint success;
+
+	if (!header_data || fd < 0) return FALSE;
+
+	entry = format_raw_find(header_data, header_len);
+
+	if (!entry || !entry->func_parse) return FALSE;
+
+	if (fstat(fd, &st) == -1)
+		{
+		printf("Failed to stat file %d\n", fd);
+		return FALSE;
+		}
+	map_len = st.st_size;
+	map_data = mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (map_data == MAP_FAILED)
+		{
+		printf("Failed to mmap of file %d\n", fd);
+		return FALSE;
+		}
+
+	success = format_raw_parse(entry, map_data, map_len, image_offset, exif_offset);
+
+	if (munmap(map_data, map_len) == -1)
+		{
+		printf("Failed to unmap file %d\n", fd);
+		}
+
+	if (success && image_offset)
+		{
+		if (lseek(fd, *image_offset, SEEK_SET) != *image_offset)
+			{
+			printf("Failed to seek to embedded image\n");
+
+			*image_offset = 0;
+			if (*exif_offset) *exif_offset = 0;
+			success = FALSE;
+			}
+		}
+
+	return success;
+}
+
+
--- a/src/format_raw.h	Wed May 18 23:52:16 2005 +0000
+++ b/src/format_raw.h	Thu May 26 18:10:52 2005 +0000
@@ -13,8 +13,16 @@
 #ifndef __FORMAT_RAW_H
 #define __FORMAT_RAW_H
 
-gint format_raw_img_exif_offsets(int fd, const void *data, const guint len,
+
+typedef gint (* FormatRawParseFunc)(const void *data, const guint len,
+				    guint *image_offset, guint *exif_offset);
+
+
+gint format_raw_img_exif_offsets(const void *data, const guint len,
 				 guint *image_offset, guint *exif_offset);
+gint format_raw_img_exif_offsets_fd(int fd, const void *header_data, const guint header_len,
+				    guint *image_offset, guint *exif_offset);
+
 
 #endif
 
--- a/src/image-load.c	Wed May 18 23:52:16 2005 +0000
+++ b/src/image-load.c	Thu May 26 18:10:52 2005 +0000
@@ -217,17 +217,23 @@
 
 	b = read(il->load_fd, &buf, sizeof(buf));
 
+	if (b > 1 &&
+	    format_raw_img_exif_offsets_fd(il->load_fd, buf, b, &offset, NULL))
+		{
+		if (debug) printf("Raw file %s contains embedded image\n", il->path);
+
+		b = read(il->load_fd, &buf, sizeof(buf));
+		}
+
 	if (b < 1)
 		{
 		image_loader_stop(il);
 		return FALSE;
 		}
 
-	format_raw_img_exif_offsets(il->load_fd, buf, b, &offset, NULL);
-
-	if (gdk_pixbuf_loader_write(il->loader, buf + offset, b - offset, NULL))
+	if (gdk_pixbuf_loader_write(il->loader, buf, b, NULL))
 		{
-		il->bytes_read += b;
+		il->bytes_read += b + offset;
 
 		if (b < sizeof(buf))
 			{