/* The GIMP -- an image manipulation program * Copyright (C) 1995, 1996, 1997 Spencer Kimball and Peter Mattis * Copyright (C) 1997 Josh MacDonald * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #ifdef HAVE_SYS_PARAM_H #include #endif #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #ifdef G_OS_WIN32 #include /* For _mkdir() */ #define mkdir(path,mode) _mkdir(path) #endif #include "libgimpmath/gimpmath.h" #include "core/core-types.h" #include "base/temp-buf.h" #include "core/gimpimage.h" #include "file-utils.h" #include "plug_in.h" static PlugInProcDef * file_proc_find_by_name (GSList *procs, const gchar *filename, gboolean skip_magic) { GSList *p; gchar *ext = strrchr (filename, '.'); if (ext) ext++; for (p = procs; p; p = g_slist_next (p)) { PlugInProcDef *proc = p->data; GSList *prefixes; if (skip_magic && proc->magics_list) continue; for (prefixes = proc->prefixes_list; prefixes; prefixes = g_slist_next (prefixes)) { if (strncmp (filename, prefixes->data, strlen (prefixes->data)) == 0) return proc; } } for (p = procs; p; p = g_slist_next (p)) { PlugInProcDef *proc = p->data; GSList *extensions; for (extensions = proc->extensions_list; ext && extensions; extensions = g_slist_next (extensions)) { gchar *p1 = ext; gchar *p2 = (gchar *) extensions->data; if (skip_magic && proc->magics_list) continue; while (*p1 && *p2) { if (tolower (*p1) != tolower (*p2)) break; p1++; p2++; } if (!(*p1) && !(*p2)) return proc; } } return NULL; } PlugInProcDef * file_proc_find (GSList *procs, const gchar *filename) { PlugInProcDef *file_proc; PlugInProcDef *size_matched_proc = NULL; GSList *all_procs = procs; FILE *ifp = NULL; gint head_size = -2; gint size_match_count = 0; gint match_val; guchar head[256]; /* First, check magicless prefixes/suffixes */ if ( (file_proc = file_proc_find_by_name (all_procs, filename, TRUE)) != NULL) return file_proc; /* Then look for magics */ while (procs) { file_proc = procs->data; procs = procs->next; if (file_proc->magics_list) { if (head_size == -2) { head_size = 0; if ((ifp = fopen (filename, "rb")) != NULL) head_size = fread ((gchar *) head, 1, sizeof (head), ifp); } if (head_size >= 4) { match_val = file_check_magic_list (file_proc->magics_list, head_size, head, ifp); if (match_val == 2) /* size match ? */ { /* Use it only if no other magic matches */ size_match_count++; size_matched_proc = file_proc; } else if (match_val) { fclose (ifp); return (file_proc); } } } } if (ifp) fclose (ifp); if (size_match_count == 1) return (size_matched_proc); /* As a last ditch, try matching by name */ return file_proc_find_by_name (all_procs, filename, FALSE); } static void file_convert_string (gchar *instr, gchar *outmem, gint maxmem, gint *nmem) { /* Convert a string in C-notation to array of char */ guchar *uin = (guchar *) instr; guchar *uout = (guchar *) outmem; guchar tmp[5], *tmpptr; gint k; while ((*uin != '\0') && ((((char *)uout) - outmem) < maxmem)) { if (*uin != '\\') /* Not an escaped character ? */ { *(uout++) = *(uin++); continue; } if (*(++uin) == '\0') { *(uout++) = '\\'; break; } switch (*uin) { case '0': case '1': case '2': case '3': /* octal */ for (tmpptr = tmp; (tmpptr-tmp) <= 3;) { *(tmpptr++) = *(uin++); if ( (*uin == '\0') || (!isdigit (*uin)) || (*uin == '8') || (*uin == '9')) break; } *tmpptr = '\0'; sscanf ((char *)tmp, "%o", &k); *(uout++) = k; break; case 'a': *(uout++) = '\a'; uin++; break; case 'b': *(uout++) = '\b'; uin++; break; case 't': *(uout++) = '\t'; uin++; break; case 'n': *(uout++) = '\n'; uin++; break; case 'v': *(uout++) = '\v'; uin++; break; case 'f': *(uout++) = '\f'; uin++; break; case 'r': *(uout++) = '\r'; uin++; break; default : *(uout++) = *(uin++); break; } } *nmem = ((gchar *) uout) - outmem; } static gint file_check_single_magic (gchar *offset, gchar *type, gchar *value, gint headsize, guchar *file_head, FILE *ifp) { /* Return values are 0: no match, 1: magic match, 2: size match */ glong offs; gulong num_testval, num_operatorval; gulong fileval; gint numbytes, k, c = 0, found = 0; gchar *num_operator_ptr, num_operator, num_test; guchar mem_testval[256]; /* Check offset */ if (sscanf (offset, "%ld", &offs) != 1) return (0); if (offs < 0) return (0); /* Check type of test */ num_operator_ptr = NULL; num_operator = '\0'; num_test = '='; if (strncmp (type, "byte", 4) == 0) { numbytes = 1; num_operator_ptr = type+4; } else if (strncmp (type, "short", 5) == 0) { numbytes = 2; num_operator_ptr = type+5; } else if (strncmp (type, "long", 4) == 0) { numbytes = 4; num_operator_ptr = type+4; } else if (strncmp (type, "size", 4) == 0) { numbytes = 5; } else if (strcmp (type, "string") == 0) { numbytes = 0; } else return (0); /* Check numerical operator value if present */ if (num_operator_ptr && (*num_operator_ptr == '&')) { if (isdigit (num_operator_ptr[1])) { if (num_operator_ptr[1] != '0') /* decimal */ sscanf (num_operator_ptr+1, "%ld", &num_operatorval); else if (num_operator_ptr[2] == 'x') /* hexadecimal */ sscanf (num_operator_ptr+3, "%lx", &num_operatorval); else /* octal */ sscanf (num_operator_ptr+2, "%lo", &num_operatorval); num_operator = *num_operator_ptr; } } if (numbytes > 0) /* Numerical test ? */ { /* Check test value */ if ((value[0] == '=') || (value[0] == '>') || (value[0] == '<')) { num_test = value[0]; value++; } if (!isdigit (value[0])) return (0); /* * to anybody reading this: is strtol's parsing behaviour (e.g. "0x" prefix) * broken on some systems or why do we do the base detection ourselves? * */ if (value[0] != '0') /* decimal */ num_testval = strtol(value, NULL, 10); else if (value[1] == 'x') /* hexadecimal */ num_testval = (unsigned long)strtoul(value+2, NULL, 16); else /* octal */ num_testval = strtol(value+1, NULL, 8); fileval = 0; if (numbytes == 5) /* Check for file size ? */ { struct stat buf; if (fstat (fileno (ifp), &buf) < 0) return (0); fileval = buf.st_size; } else if (offs + numbytes <= headsize) /* We have it in memory ? */ { for (k = 0; k < numbytes; k++) fileval = (fileval << 8) | (long)file_head[offs+k]; } else /* Read it from file */ { if (fseek (ifp, offs, SEEK_SET) < 0) return (0); for (k = 0; k < numbytes; k++) fileval = (fileval << 8) | (c = getc (ifp)); if (c == EOF) return (0); } if (num_operator == '&') fileval &= num_operatorval; if (num_test == '<') found = (fileval < num_testval); else if (num_test == '>') found = (fileval > num_testval); else found = (fileval == num_testval); if (found && (numbytes == 5)) found = 2; } else if (numbytes == 0) /* String test */ { file_convert_string ((char *)value, (char *)mem_testval, sizeof (mem_testval), &numbytes); if (numbytes <= 0) return (0); if (offs + numbytes <= headsize) /* We have it in memory ? */ { found = (memcmp (mem_testval, file_head+offs, numbytes) == 0); } else /* Read it from file */ { if (fseek (ifp, offs, SEEK_SET) < 0) return (0); found = 1; for (k = 0; found && (k < numbytes); k++) { c = getc (ifp); found = (c != EOF) && (c == (int)mem_testval[k]); } } } return found; } gint file_check_magic_list (GSList *magics_list, gint headsize, guchar *head, FILE *ifp) { /* Return values are 0: no match, 1: magic match, 2: size match */ gchar *offset; gchar *type; gchar *value; gint and = 0; gint found = 0; gint match_val; while (magics_list) { if ((offset = (gchar *)magics_list->data) == NULL) break; if ((magics_list = magics_list->next) == NULL) break; if ((type = (gchar *)magics_list->data) == NULL) break; if ((magics_list = magics_list->next) == NULL) break; if ((value = (gchar *)magics_list->data) == NULL) break; magics_list = magics_list->next; match_val = file_check_single_magic (offset, type, value, headsize, head, ifp); if (and) found = found && match_val; else found = match_val; and = (strchr (offset, '&') != NULL); if ((!and) && found) return match_val; } return 0; } TempBuf * make_thumb_tempbuf (GimpImage *gimage) { gint w, h; if (gimage->width<=80 && gimage->height<=60) { w = gimage->width; h = gimage->height; } else { /* Ratio molesting to fit within .xvpic thumbnail size limits */ if (60 * gimage->width < 80 * gimage->height) { h = 60; w = (60 * gimage->width) / gimage->height; if (w == 0) w = 1; } else { w = 80; h = (80 * gimage->height) / gimage->width; if (h == 0) h = 1; } } /*printf("tn: %d x %d -> ", w, h);fflush(stdout);*/ return gimp_viewable_get_preview (GIMP_VIEWABLE (gimage), w, h); } /* The readXVThumb function source may be re-used under the XFree86-style license. */ guchar * readXVThumb (const gchar *fnam, gint *w, gint *h, gchar **imginfo /* caller frees if != NULL */) { FILE *fp; const gchar *P7_332 = "P7 332"; gchar P7_buf[7]; gchar linebuf[200]; guchar *buf; gint twofivefive; void *ptr; *w = *h = 0; *imginfo = NULL; fp = fopen (fnam, "rb"); if (!fp) return NULL; fread (P7_buf, 6, 1, fp); if (strncmp(P7_buf, P7_332, 6)!=0) { g_warning ("Thumbnail doesn't have the 'P7 332' header."); fclose (fp); return NULL; } /*newline*/ fread (P7_buf, 1, 1, fp); do { ptr = fgets(linebuf, 199, fp); if ((strncmp(linebuf, "#IMGINFO:", 9) == 0) && (linebuf[9] != '\0') && (linebuf[9] != '\n')) { if (linebuf[strlen(linebuf)-1] == '\n') linebuf[strlen(linebuf)-1] = '\0'; if (linebuf[9] != '\0') { if (*imginfo) g_free(*imginfo); *imginfo = g_strdup (&linebuf[9]); } } } while (ptr && linebuf[0]=='#'); /* keep throwing away comment lines */ if (!ptr) { /* g_warning("Thumbnail ended - not an image?"); */ fclose (fp); return NULL; } sscanf(linebuf, "%d %d %d\n", w, h, &twofivefive); if (twofivefive!=255) { g_warning ("Thumbnail is of funky depth."); fclose (fp); return NULL; } if ((*w)<1 || (*h)<1 || (*w)>80 || (*h)>60) { g_warning ("Thumbnail size bad. Corrupted?"); fclose (fp); return NULL; } buf = g_malloc ((*w) * (*h)); fread (buf, (*w) * (*h), 1, fp); fclose (fp); return buf; } gboolean file_save_thumbnail (GimpImage *gimage, const gchar *full_source_filename, TempBuf *tempbuf) { gint i,j; gint w,h; guchar *tbd; gchar *pathname; gchar *filename; gchar *xvpathname; gchar *thumbnailname; GimpImageBaseType basetype; FILE *fp; struct stat statbuf; if (stat (full_source_filename, &statbuf) != 0) { return FALSE; } pathname = g_dirname (full_source_filename); filename = g_basename (full_source_filename); /* Don't free! */ xvpathname = g_strconcat (pathname, G_DIR_SEPARATOR_S, ".xvpics", NULL); thumbnailname = g_strconcat (xvpathname, G_DIR_SEPARATOR_S, filename, NULL); tbd = temp_buf_data (tempbuf); w = tempbuf->width; h = tempbuf->height; /*printf("tn: %d x %d\n", w, h);fflush(stdout);*/ mkdir (xvpathname, 0755); fp = fopen (thumbnailname, "wb"); g_free (pathname); g_free (xvpathname); g_free (thumbnailname); if (fp) { basetype = gimp_image_base_type (gimage); fprintf (fp, "P7 332\n#IMGINFO:%dx%d %s (%d %s)\n" "#END_OF_COMMENTS\n%d %d 255\n", gimage->width, gimage->height, (basetype == RGB) ? "RGB" : (basetype == GRAY) ? "Greyscale" : (basetype == INDEXED) ? "Indexed" : "(UNKNOWN COLOUR TYPE)", (int)statbuf.st_size, (statbuf.st_size == 1) ? "byte" : "bytes", w, h); switch (basetype) { case INDEXED: case RGB: for (i=0; i>5)<<5) | ((g>>5)<<2) | (b>>6), fp); rerr = r - ( (r>>5) * 255 ) / 7; gerr = g - ( (g>>5) * 255 ) / 7; berr = b - ( (b>>6) * 255 ) / 3; } } break; case GRAY: for (i=0; i>5)<<5) | ((b3>>5)<<2) | (b2>>6), fp); b2err = b2 - ( (b2>>6) * 255 ) / 3; b3err = b3 - ( (b3>>5) * 255 ) / 7; } } break; default: g_warning("UNKNOWN GIMAGE TYPE IN THUMBNAIL SAVE"); break; } fclose (fp); } else /* Error writing thumbnail */ { return FALSE; } return TRUE; }