/* * GraphApp - Cross-Platform Graphics Programming Library. * * File: image.c -- cross-platform image type. * Platform: Neutral Version: 2.40 Date: 1998/05/05 * * Version: 2.30 Changes: Original version by Lachlan Patrick. * Version: 2.40 Changes: Faster drawing, image scaling and cropping. */ /* Copyright (C) 1993-1998 Lachlan Patrick This file is part of GraphApp, a cross-platform C graphics library. GraphApp is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License. GraphApp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the file COPYLIB.TXT for details. */ /* * Image * ----- * An image is a platform-indepedent representation of a * rectangular picture, in RGB colour format. There are two * possible formats the picture can use: 8-bit indexed colour, * and 32-bit true colour (RGB plus alpha channel). * * An image in 8-bit format has the following properties: * - depth is set to 8 * - pixels is an array of (width*height) bytes * - cmapsize is set to a non-zero value * - cmap is an array of cmapsize rgb values * * An image in 32-bit format has the following properties: * - depth is set to 32 * - pixels is an array of (width * height) rgb values * - cmapsize is zero and cmap is NULL (an empty array) * * Any other depth is deliberately not supported. * Transparency is handled in the alpha channel of each rgb value * (either inside the cmap, or in the pixels array itself). */ #include "internal.h" /* * Create a new image: */ image newimage(int width, int height, int depth) { image img; if ((depth != 8) && (depth != 32)) return NULL; img = create(struct imagedata); if (! img) return img; img->width = width; img->height = height; if (depth == 8) { img->depth = 8; img->pixels = array(width*height, GAbyte); } else { img->depth = 32; img->pixels = (GAbyte *) array(width*height, rgb); } return img; } /* * Make a new copy of an image: */ image copyimage(image img) { image new_img; if (! img) return img; new_img = newimage(img->width, img->height, img->depth); setpixels(new_img, img->pixels); setpalette(new_img, img->cmapsize, img->cmap); return new_img; } /* * Delete an image: */ void delimage(image img) { if (img) { discard(img->cmap); discard(img->pixels); discard(img); } } /* * Print an image: */ #if DEBUG PROTECTED void printimage(FILE *file, image img) { int i; long w, h, n; fprintf(file, "Pixmap:\n"); fprintf(file, " depth = %d\n", img->depth); fprintf(file, " width = %d\n", img->width); fprintf(file, " height = %d\n", img->height); fprintf(file, " cmapsize = %d\n", img->cmapsize); fprintf(file, " cmap:\n"); for (i=0; i < img->cmapsize; i++) fprintf(file, " %-02.2X = %-08.8lX\n", i, img->cmap[i]); fprintf(file, " pixels:\n"); for (h=0; h < img->height; h++) { fprintf(file, " ["); for (w=0; w < (img->width * img->depth / 8); w++) { n = h * (img->width * img->depth / 8) + w; fprintf(file, "%-02.2X", img->pixels[n]); } fprintf(file, "]\n"); } } #endif /* * Discover the dimensions of the image: */ int imagedepth(image img) { return ((img)?(img->depth):0);} int imagewidth(image img) { return ((img)?(img->width):0);} int imageheight(image img) { return ((img)?(img->height):0);} /* * Change an image's pixels: */ void setpixels(image img, GAbyte pixels[]) { long i, length; if (! img) return; length = img->width * img->height; if (img->depth > 8) length = length * sizeof(rgb); for (i=0; i < length; i++) img->pixels[i] = pixels[i]; } /* * Return an image's pixel array: */ GAbyte * getpixels(image img) { if (img) return img->pixels; else return NULL; } /* * Change an image's cmap: */ void setpalette(image img, int cmapsize, rgb *cmap) { int i; if (! img) return; discard(img->cmap); img->cmapsize = cmapsize; img->cmap = array(cmapsize, rgb); for (i=0; i < cmapsize; i++) img->cmap[i] = cmap[i]; } /* * Return information about an image's cmap: */ rgb * getpalette(image img) { if (img) return img->cmap; else return NULL; } int getpalettesize(image img) { if (img) return img->cmapsize; else return 0; } /* * Try to generate an 8-bit version of an image: * If there are less than 256 unique colours in a 32-bit * image, this routine will return the corresponding * indexed 8-bit image. * Returns NULL if more than 256 colours are found. */ static image fast_find_cmap (image img) { image new_img; long i, j, length; rgb * pixel32; GAbyte * pixel8; int cmapsize, low, high, mid; rgb col; rgb cmap[256]; pixel32 = (rgb *) img->pixels; /* the first colour goes into the cmap automatically: */ length = img->width * img->height; cmapsize = 0; mid = 0; for (i=0; i < length; i++) { col = *pixel32 ++; /* only allow one transparent colour in the cmap: */ if (col & 0xF0000000UL) col = 0xFFFFFFFFUL; /* transparent */ else col &= 0x00FFFFFFUL; /* opaque */ /* binary search the cmap: */ low = 0; high = cmapsize - 1; while (low <= high) { mid = (low+high)/2; if (col < cmap[mid]) high = mid - 1; else if (col > cmap[mid]) low = mid + 1; else break; } if (high < low) { /* didn't find colour in cmap, insert it: */ if (cmapsize >= 256) return NULL; for (j=cmapsize; j > low; j--) cmap[j] = cmap[j-1]; cmap[low] = col; cmapsize ++; } } /* now create the 8-bit indexed image: */ new_img = newimage(img->width, img->height, 8); if (! new_img) return new_img; setpalette(new_img, cmapsize, cmap); /* now convert each 32-bit pixel into an 8-bit pixel: */ pixel32 = (rgb *) img->pixels; pixel8 = (GAbyte *) new_img->pixels; for (i=0; i < length; i++) { col = *pixel32 ++; /* only allow one transparent colour in the cmap: */ if (col & 0xF0000000UL) col = 0xFFFFFFFFUL; /* transparent */ else col &= 0x00FFFFFFUL; /* opaque */ /* binary search the cmap (the colour must be there): */ low = 0; high = cmapsize - 1; while (low <= high) { mid = (low+high)/2; if (col < cmap[mid]) high = mid - 1; else if (col > cmap[mid]) low = mid + 1; else break; } if (high < low) { /* impossible situation */ delimage(new_img); return NULL; } *pixel8 = mid; pixel8 ++; } return new_img; } /* * Try to generate an 8-bit version of an image: * This routine will approximate a 32-bit image using a * 7x7x5 colour cube. * If it runs out of memory, it returns NULL. */ static image fast_generate_cmap (image img) { image new_img; long i, length, col; int r, g, b, value; rgb * pixel32; GAbyte * pixel8; rgb cmap[256]; /* Generate the colour map: */ for (r=0; r<7; r++) /* 7x7x5 colour cube */ for (g=0; g<7; g++) for (b=0; b<5; b++) cmap [r*35 + g*5 + b] = rgb(r,g,b); for (i=0; i<9; i++) /* greyscale ramp */ { value = 255 * i / 8; cmap [254 - 8 + i] = rgb(value, value, value); } cmap [255] = 0xFFFFFFFFUL; /* transparent */ /* Generate the 8-bit indexed image: */ new_img = newimage(img->width, img->height, 8); if (! new_img) return new_img; setpalette(new_img, 256, cmap); /* Translate the pixels from 32-bit to 8-bit: */ length = img->width * img->height; pixel32 = (rgb *) img->pixels; pixel8 = (GAbyte *) new_img->pixels; for (i=0; i < length; i++) { col = *pixel32 ++; r = getred(col); g = getgreen(col); b = getblue(col); if (getalpha(col) > 0x7F) /* transparent */ value = 255; else if ((r == g) && (r == b)) /* grey */ { r = (r + 16) / 32; if (r == 0) value = 0; /* black */ else value = 254 - 8 + r; } else /* map to 7x7x5 colour cube */ { r = (r + 21) / 42; g = (g + 21) / 42; b = (b + 32) / 64; value = r*35 + g*5 + b; } *pixel8 = value; pixel8 ++; } return new_img; } /* * Try to generate an 8-bit version of a 32-bit image: * Return NULL on failure. */ image convert32to8 (image img) { image new_img; if (! img) return img; if (img->depth <= 8) return copyimage(img); new_img = fast_find_cmap(img); if (! new_img) new_img = fast_generate_cmap(img); return new_img; } /* * Try to generate a 32-bit version of an 8-bit image: * Return NULL if there is no memory left. */ image convert8to32 (image img) { image new_img; long i; rgb *pixel32; GAbyte *pixel8; GAbyte value; if (! img) return img; new_img = newimage(img->width, img->height, 32); if (! new_img) return new_img; pixel32 = (rgb *) new_img->pixels; pixel8 = (GAbyte *) img->pixels; for (i=img->width * img->height; i; i--) { value = *pixel8 ++; if (value >= img->cmapsize) value = img->cmapsize - 1; *pixel32 ++ = img->cmap[value]; } return new_img; } /* * Sort an image's colour map, eliminating redudancies. * This operation transforms an existing image. */ typedef int (*qsort_func)(const void *a, const void *b); static int compare_freq(long *a, long *b) { long freq_a, freq_b; int value_a, value_b; freq_a = (*a) >> 8; freq_b = (*b) >> 8; value_a = (*a) & 0x00FF; value_b = (*b) & 0x00FF; if (freq_a < freq_b) return (+1); else if (freq_a > freq_b) return (-1); else return (value_a - value_b); } void sortpalette(image img) { long i, j, length; int old_value, new_value; rgb col; int new_size; long * histogram; GAbyte * translate; rgb * new_cmap; if (! img) return; if (img->depth > 8) return; histogram = array(256, long); translate = array(256, GAbyte); /* Generate a colour histogram: */ length = img->width * img->height; for (i=0; i < length; i++) histogram[img->pixels[i]] ++; /* Place colour indexes in low byte of histogram: */ for (i=0; i < 256; i++) { histogram[i] <<= 8; histogram[i] |= i; } /* Sort the histogram in decreasing frequency order: */ qsort(histogram, 256, sizeof(long), (qsort_func) compare_freq); /* Generate a colour translation table: */ new_size = img->cmapsize; for (i=255; i >= 0; i--) { old_value = histogram[i] & 0x00FFL; new_value = i; col = img->cmap[i]; /* coalesce identical colours in cmap */ for (j=i-1; j >= 0; j--) { if (img->cmap[j] == col) new_value = j; } translate[old_value] = new_value; /* find smallest useless colour */ if ((histogram[i] >> 8) == 0) new_size = i; } /* Generate a sorted colour map: */ new_cmap = array(new_size, rgb); for (i=0; i < new_size; i++) { old_value = histogram[i] & 0x00FFL; new_value = i; new_cmap[new_value] = img->cmap[old_value]; } /* Change the existing colour map: */ img->cmapsize = new_size; for (i=0; i < new_size; i++) img->cmap[i] = new_cmap[i]; discard(new_cmap); /* Translate the pixels to the new colour map: */ for (i=0 ; i < length; i++) img->pixels[i] = translate[img->pixels[i]]; /* Clean up and return new image: */ discard(translate); discard(histogram); } #ifdef UNUSED /* * Load and save images (utility functions): */ static int string_ends_with(const char *name, const char *ending) { int i, j, result; if (name == NULL) return (ending == NULL); result = 1; for (i=0; name[i]; ) i = i + 1; for (j=0; ending[j]; ) j = j + 1; while (result && (i >= 0) && (j >= 0)) { if (tolower(name[i]) != tolower(ending[j])) result = 0; i = i - 1; j = j - 1; } if ((i == 0) && (j > 0)) result = 0; return result; } static unsigned char read_hex_byte(FILE *file) { int ch; int i, value; unsigned char result = 0; ch = 0; while (ch != EOF) { if ((ch = getc(file)) != '0') continue; if ((ch = getc(file)) != 'x') continue; for (i=0; i<2; i++) { ch = getc(file); if (isdigit(ch)) value = ch - '0'; else if (isalpha(ch)) value = tolower(ch) - 'a' + 10; else return result; result = (result << 4) | value; } break; } return result; } static unsigned long read_hex_long(FILE *file) { int ch; int i, value; unsigned long result = 0; ch = 0; while (ch != EOF) { if ((ch = getc(file)) != '0') continue; if ((ch = getc(file)) != 'x') continue; for (i=0; i<8; i++) { ch = getc(file); if (isdigit(ch)) value = ch - '0'; else if (isalpha(ch)) { ch = tolower(ch); if (ch > 'f') return result; value = ch - 'a' + 10; } else return result; result = (result << 4) | value; } break; } return result; } static const char * header_comment = "/* GraphApp image type 1 */\n"; static const char * depth_comment = "/* depth = %d */\n"; static const char * width_comment = "/* width = %d */\n"; static const char * height_comment = "/* height = %d */\n"; static const char * cmapsize_comment = "/* cmapsize = %d */\n"; static image load_header_image_file(FILE *file) { char line[100]; long i, size; int width = 0, height = 0, depth = 8; int cmapsize = 0; rgb * cmap = NULL; rgb *pixel32; GAbyte *pixel8; image img = NULL; if (file == NULL) return NULL; if (fgets(line, sizeof(line)-2, file) == NULL) return NULL; if (strcmp(line, header_comment)) return NULL; for (i=0; i<4; i++) { if (fgets(line, sizeof(line)-2, file) == NULL) return NULL; if (! strncmp(line, depth_comment, 12)) depth = atoi(line+12); if (! strncmp(line, width_comment, 12)) width = atoi(line+12); if (! strncmp(line, height_comment, 12)) height = atoi(line+12); if (! strncmp(line, cmapsize_comment, 14)) cmapsize = atoi(line+14); } img = newimage(width, height, depth); if (img == NULL) return NULL; if (depth <= 8) { if (fgets(line, sizeof(line)-2, file) == NULL) return NULL; if (strncmp(line, "rgb ", 4) != 0) { delimage(img); return NULL; } for (i=0; icmapsize = cmapsize; img->cmap = cmap; } pixel32 = (rgb *) img->pixels; pixel8 = img->pixels; size = (long) width * height; if (fgets(line, sizeof(line)-2, file) == NULL) return NULL; if (depth <= 8) { for (i=0; idepth); fprintf(file, width_comment, img->width); fprintf(file, height_comment, img->height); fprintf(file, cmapsize_comment, img->cmapsize); if (img->depth <= 8) { fprintf(file, "rgb %s_cmap [] = {\n", name); for (i=0; icmapsize; i++) fprintf(file, "\t0x%-8.8lXUL,\n", img->cmap[i]); fprintf(file, "};\n"); } pixel32 = (rgb *) img->pixels; pixel8 = img->pixels; size = (long) img->width * img->height; width = img->width; if (img->depth <= 8) { fprintf(file, "byte %s_pixels [] = {", name); if (width > 12) width = 12; for (i=0; i 5) width = 5; for (i=0; idepth); fprintf(file, "\t%d,\t/* width */\n", img->width); fprintf(file, "\t%d,\t/* height */\n", img->height); fprintf(file, "\t%d,\t/* cmapsize */\n", img->cmapsize); if (img->depth <= 8) { fprintf(file, "\t%s_cmap,\n", name); fprintf(file, "\t%s_pixels\n", name); } else { fprintf(file, "\t(rgb *) 0\n"); fprintf(file, "\t(byte *) %s_pixels\n", name); } fprintf(file, "};\n"); fprintf(file, "image %s_image = & %s_imagedata;\n", name, name); fprintf(file, "\n"); } static image load_header_image(const char *filename) { FILE *file; image img; file = fopen(filename, "rt"); img = load_header_image_file(file); fclose(file); return img; } static char * base_file_name(const char *filename) { char *name = NULL; int i, start, end; end = strlen(filename); while (filename[end] != '.') end--; for (start=end; start > 0; start--) { if ((filename[start] == '\\') || (filename[start] == '/') || (filename[start] == ':')) { start ++; break; } } for (i=start; i < end; i++) append(name, tolower(filename[i])); return name; } static void save_header_image(image img, const char *filename) { FILE *file; char *name; file = fopen(filename, "wt"); name = base_file_name(filename); save_header_image_file(file, name, img); discard(name); fclose(file); } /* * Top-level functions for loading and saving images: */ image loadimage(const char *filename) { if (string_ends_with(filename, ".gif")) return load_gif(filename); else if (string_ends_with(filename, ".h")) return load_header_image(filename); else if (string_ends_with(filename, ".img")) return load_header_image(filename); else return NULL; } void saveimage(image img, const char *filename) { if (string_ends_with(filename, ".gif")) save_gif(img, filename); else if (string_ends_with(filename, ".h")) save_header_image(img, filename); else if (string_ends_with(filename, ".img")) save_header_image(img, filename); } #endif /* * Changing an rgb's value: */ rgb darker(rgb pixel) { int r, g, b; if (getalpha(pixel) > 0x7F) return Transparent; r = getred(pixel); g = getgreen(pixel); b = getblue(pixel); return rgb((r+1)*3/4,(g+1)*3/4,(b+1)*3/4); } rgb brighter(rgb pixel) { int r, g, b; if (getalpha(pixel) > 0x7F) return Transparent; r = getred(pixel); g = getgreen(pixel); b = getblue(pixel); r = (r) * 4 / 3; if (r > 255) r = 255; g = (g) * 4 / 3; if (g > 255) g = 255; b = (b) * 4 / 3; if (b > 255) b = 255; return rgb(r,g,b); } static rgb monochrome(rgb pixel) { int min, max, g, b; if (getalpha(pixel) > 0x7F) return Transparent; max = min = getred(pixel); g = getgreen(pixel); if (g < min) min = g; else if (g > max) max = g; b = getblue(pixel); if (b < min) min = b; else if (b > max) max = b; if (min > 0xE0) pixel = White; else if (max < 0x10) pixel = Black; else if (max < 0x60) pixel = Black; else if (max < 0xD0) pixel = Black; else pixel = White; return pixel; } static rgb greyscale(rgb pixel) { int min, max, g, b; if (getalpha(pixel) > 0x7F) return Transparent; max = min = getred(pixel); g = getgreen(pixel); if (g < min) min = g; else if (g > max) max = g; b = getblue(pixel); if (b < min) min = b; else if (b > max) max = b; if (min > 0xE0) pixel = White; else if (max < 0x10) pixel = Black; else if (max < 0x60) pixel = DarkGrey; else if (max < 0xD0) pixel = Grey; else pixel = LightGrey; return pixel; } /* * Determine pixel values from an image: */ PROTECTED rgb get_image_pixel(image img, int x, int y) { int value; rgb pixel; if ((x < 0) || (x >= img->width)) return Transparent; if ((y < 0) || (y >= img->height)) return Transparent; if (img->depth <= 8) { value = img->pixels[y*img->width + x]; pixel = img->cmap[value]; } else { pixel = ((rgb *)(img->pixels))[y*img->width + x]; } if (getalpha(pixel) > 0x7F) return Transparent; return (pixel & White); } PROTECTED rgb get_monochrome_pixel(image img, int x, int y) { return monochrome(get_image_pixel(img, x, y)); } PROTECTED rgb get_grey_pixel(image img, int x, int y) { return greyscale(get_image_pixel(img, x, y)); } /* * Return an image scaled to a new width and/or height. * The source rectangle sr can be used to crop the source image. * The returned image should be deleted using del() when * it is no longer needed. */ static void scale_8_bit_image(image dest, image src, rect dr, rect sr) { int value; long x, y; long dx, dy, sx, sy; long dw, dh, sw, sh; GAbyte * src_pixels = src->pixels; GAbyte * dest_pixels = dest->pixels; dw = dest->width; dh = dest->height; sw = src->width; sh = src->height; for (y=0; y < dr.height; y++) for (x=0; x < dr.width; x++) { sy = sr.y + y * sr.height / dr.height; sx = sr.x + x * sr.width / dr.width; if ((sx >= 0) && (sx < sw) && (sy >= 0) && (sy < sh)) value = src_pixels [sy * sw + sx]; else value = 0; dy = dr.y + y; dx = dr.x + x; if ((dx >= 0) && (dx < dw) && (dy >= 0) && (dy < dh)) dest_pixels [dy * dw + dx] = value; } } static void scale_32_bit_image(image dest, image src, rect dr, rect sr) { rgb value; long x, y; long dx, dy, sx, sy; long dw, dh, sw, sh; rgb * src_pixels = (rgb *) src->pixels; rgb * dest_pixels = (rgb *) dest->pixels; dw = dest->width; dh = dest->height; sw = src->width; sh = src->height; for (y=0; y < dr.height; y++) for (x=0; x < dr.width; x++) { sy = sr.y + y * sr.height / dr.height; sx = sr.x + x * sr.width / dr.width; if ((sx >= 0) && (sx < sw) && (sy >= 0) && (sy < sh)) value = src_pixels [sy * sw + sx]; else value = 0; dy = dr.y + y; dx = dr.x + x; if ((dx >= 0) && (dx < dw) && (dy >= 0) && (dy < dh)) dest_pixels [dy * dw + dx] = value; } } image scaleimage(image src, rect dr, rect sr) { image dest; if (! src) return NULL; dest = newimage(dr.width, dr.height, src->depth); if (! dest) return NULL; if (src->depth == 8) { setpalette(dest, src->cmapsize, src->cmap); scale_8_bit_image(dest, src, dr, sr); } else if (src->depth == 32) { scale_32_bit_image(dest, src, dr, sr); } else { del(dest); dest = NULL; } return dest; } /* * Functions for drawing an image: */ static int get_mono_pixval(image src, image dest, int x, int y) { int i; rgb pixel = get_monochrome_pixel(src, x, y); for (i=0; i < dest->cmapsize; i++) if (pixel == dest->cmap[i]) return i; return dest->cmapsize - 1; } static int get_grey_pixval(image src, image dest, int x, int y) { int i; rgb pixel = get_grey_pixel(src, x, y); for (i=0; i < dest->cmapsize; i++) if (pixel == dest->cmap[i]) return i; return dest->cmapsize - 1; } void drawmonochrome(image src, rect dr, rect sr) { image dest; int x, y; rgb cmap[3] = {Black, White, Transparent}; if (! src) return; dest = newimage(src->width, src->height, 8); setpalette(dest, 3, cmap); for (y=0; y < dest->height; y++) for (x=0; x < dest->width; x++) dest->pixels[y * dest->width + x] = get_mono_pixval(src, dest, x, y); drawimage(dest, dr, sr); del(dest); } void drawgreyscale(image src, rect dr, rect sr) { image dest; int x, y; rgb cmap[6] = {Black, DarkGrey, Grey, LightGrey, White, Transparent}; if (! src) return; dest = newimage(src->width, src->height, 8); setpalette(dest, 6, cmap); for (y=0; y < dest->height; y++) for (x=0; x < dest->width; x++) dest->pixels[y * dest->width + x] = get_grey_pixval(src, dest, x, y); drawimage(dest, dr, sr); del(dest); } void drawdarker(image src, rect dr, rect sr) { image dest = NULL; int i, x, y; rgb *newcmap = NULL, *oldcmap = NULL; rgb *pixels; if (! src) return; if (src->depth == 8) { newcmap = array(src->cmapsize, rgb); for (i=0; i < src->cmapsize; i++) newcmap[i] = darker(src->cmap[i]); oldcmap = src->cmap; src->cmap = newcmap; dest = src; } else if (src->depth == 32) { dest = newimage(src->width, src->height, src->depth); if (dest) { pixels = (rgb *) dest->pixels; for (y=0; y < dest->height; y++) for (x=0; x < dest->width; x++) pixels[y * dest->width + x] = darker(get_image_pixel(src, x, y)); } else { dest = src; } } drawimage(dest, dr, sr); if (dest != src) del(dest); if (src->cmap == newcmap) src->cmap = oldcmap; discard(newcmap); } void drawbrighter(image src, rect dr, rect sr) { image dest = NULL; int i, x, y; rgb *newcmap = NULL, *oldcmap = NULL; rgb *pixels; if (! src) return; if (src->depth == 8) { newcmap = array(src->cmapsize, rgb); for (i=0; i < src->cmapsize; i++) newcmap[i] = brighter(src->cmap[i]); oldcmap = src->cmap; src->cmap = newcmap; dest = src; } else if (src->depth == 32) { dest = newimage(src->width, src->height, src->depth); if (dest) { pixels = (rgb *) dest->pixels; for (y=0; y < dest->height; y++) for (x=0; x < dest->width; x++) pixels[y * dest->width + x] = brighter(get_image_pixel(src,x,y)); } else { dest = src; } } drawimage(dest, dr, sr); if (dest != src) del(dest); if (src->cmap == newcmap) src->cmap = oldcmap; discard(newcmap); }