| /* | |
| * 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, byte); | |
| } | |
| else { | |
| img->depth = 32; | |
| img->pixels = (byte *) 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, byte 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: | |
| */ | |
| byte * 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; | |
| byte * 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 = (byte *) 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; | |
| byte * 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 = (byte *) 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; | |
| byte *pixel8; | |
| byte 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 = (byte *) 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; | |
| byte * translate; | |
| rgb * new_cmap; | |
| if (! img) | |
| return; | |
| if (img->depth > 8) | |
| return; | |
| histogram = array(256, long); | |
| translate = array(256, byte); | |
| /* 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; | |
| byte *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; i<cmapsize; i++) { | |
| append(cmap, read_hex_long(file)); | |
| } | |
| if (fgets(line, sizeof(line)-2, file) == NULL) | |
| return NULL; | |
| img->cmapsize = 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; i<size; i++) | |
| pixel8[i] = read_hex_byte(file); | |
| } | |
| else { | |
| for (i=0; i<size; i++) | |
| pixel32[i] = read_hex_long(file); | |
| } | |
| return img; | |
| } | |
| static void save_header_image_file(FILE *file, char *name, image img) | |
| { | |
| long i, size; | |
| int width; | |
| rgb * pixel32; | |
| byte *pixel8; | |
| if (file == NULL) | |
| return; | |
| if (img == NULL) | |
| return; | |
| fprintf(file, header_comment); | |
| fprintf(file, depth_comment, img->depth); | |
| 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; i<img->cmapsize; 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<size; i++) { | |
| if ((i%width) == 0) | |
| fprintf(file, "\n "); | |
| fprintf(file, "0x%-2.2X, ", pixel8[i]); | |
| } | |
| fprintf(file, "\n};\n"); | |
| } | |
| else { | |
| fprintf(file, "rgb %s_pixels [] = {", name); | |
| if (width > 5) width = 5; | |
| for (i=0; i<size; i++) { | |
| if ((i%width) == 0) | |
| fprintf(file, "\n "); | |
| fprintf(file, "0x%-8.8lXUL, ", pixel32[i]); | |
| } | |
| fprintf(file, "\n};\n"); | |
| } | |
| fprintf(file, "imagedata %s_imagedata = {\n", name); | |
| fprintf(file, "\t%d,\t/* depth */\n", img->depth); | |
| 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; | |
| byte * src_pixels = src->pixels; | |
| byte * 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); | |
| } |