Created
November 7, 2015 01:16
-
-
Save technosaurus/3603768de1f83d670546 to your computer and use it in GitHub Desktop.
use lighter image loaders for jpg,png and svg + support gif,bmp,tga and others
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file image.c | |
* @author Joe Wingbermuehle | |
* @date 2005-2007 | |
* | |
* @brief Functions to load images. | |
* | |
*/ | |
#include "jwm.h" | |
#ifndef MAKE_DEPEND | |
/* We should include png.h here. See jwm.h for an explanation. */ | |
# ifdef USE_XPM | |
# include <X11/xpm.h> | |
# endif | |
# ifdef USE_JPEG | |
# include <jpeglib.h> | |
# endif | |
# ifdef USE_RSVG | |
# include <cairo.h> | |
# include <cairo-svg.h> | |
# include <librsvg/rsvg.h> | |
# endif | |
# ifdef USE_STB_IMAGE | |
# define STBI_NO_HDR | |
# define STBI_NO_LINEAR | |
# define STB_IMAGE_IMPLEMENTATION | |
# include "stb_image.h" | |
# endif | |
# ifdef USE_NANOSVG | |
# define NANOSVG_IMPLEMENTATION | |
# include "nanosvg.h" | |
# define NANOSVGRAST_IMPLEMENTATION | |
# include "nanosvgrast.h" | |
# endif | |
#endif /* MAKE_DEPEND */ | |
#include "image.h" | |
#include "main.h" | |
#include "error.h" | |
#include "color.h" | |
#include "misc.h" | |
#ifdef USE_STB_IMAGE | |
static ImageNode *LoadstbImage(const char *fileName); | |
#endif | |
#if defined USE_RSVG || defined USE_NANOSVG | |
static ImageNode *LoadSVGImage(const char *fileName); | |
#endif | |
#ifdef USE_JPEG | |
static ImageNode *LoadJPEGImage(const char *fileName); | |
#endif | |
#ifdef USE_PNG | |
static ImageNode *LoadPNGImage(const char *fileName); | |
#endif | |
#ifdef USE_XPM | |
static ImageNode *LoadXPMImage(const char *fileName); | |
#endif | |
#ifdef USE_XBM | |
static ImageNode *LoadXBMImage(const char *fileName); | |
#endif | |
#ifdef USE_ICONS | |
static ImageNode *CreateImageFromXImages(XImage *image, XImage *shape); | |
#endif | |
#ifdef USE_XPM | |
static int AllocateColor(Display *d, Colormap cmap, char *name, | |
XColor *c, void *closure); | |
static int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n, | |
void *closure); | |
#endif | |
/** Load an image from the specified file. */ | |
ImageNode *LoadImage(const char *fileName) | |
{ | |
const unsigned nameLength = strlen(fileName); | |
ImageNode *result = NULL; | |
if(!fileName) { | |
return result; | |
} | |
/* Attempt to load the file as a PNG image. */ | |
#if defined USE_PNG || defined USE_STB_IMAGE | |
if(nameLength >= 4 && !StrCmpNoCase(&fileName[nameLength - 4], ".png")) { | |
#ifdef USE_PNG | |
result = LoadPNGImage(fileName); | |
#else | |
result = LoadstbImage(fileName); | |
#endif | |
if(result) { | |
return result; | |
} | |
} | |
#endif | |
/* Attempt to load the file as a JPEG image. */ | |
#if defined USE_JPEG || defined USE_STB_IMAGE | |
if( (nameLength >= 4 | |
&& !StrCmpNoCase(&fileName[nameLength - 4], ".jpg")) | |
|| (nameLength >= 5 | |
&& !StrCmpNoCase(&fileName[nameLength - 5], ".jpeg"))) { | |
#ifdef USE_JPEG | |
result = LoadJPEGImage(fileName); | |
#else | |
result = LoadstbImage(fileName); | |
#endif | |
if(result) { | |
return result; | |
} | |
} | |
#endif | |
/* Attempt to load the file as an SVG image. */ | |
#if defined USE_RSVG || defined USE_STB_IMAGE | |
if(nameLength >= 4 && !StrCmpNoCase(&fileName[nameLength - 4], ".svg")) { | |
result = LoadSVGImage(fileName); | |
if(result) { | |
return result; | |
} | |
} | |
#endif | |
/* Attempt to load the file as an XPM image. */ | |
#ifdef USE_XPM | |
if(nameLength >= 4 && !StrCmpNoCase(&fileName[nameLength - 4], ".xpm")) { | |
result = LoadXPMImage(fileName); | |
if(result) { | |
return result; | |
} | |
} | |
#endif | |
/* Attempt to load the file as an XBM image. */ | |
#ifdef USE_XBM | |
if(nameLength >= 4 && !StrCmpNoCase(&fileName[nameLength - 4], ".xbm")) { | |
result = LoadXBMImage(fileName); | |
if(result) { | |
return result; | |
} | |
} | |
#endif | |
return result; | |
} | |
/** Load an image from the specified XPM data. */ | |
ImageNode *LoadImageFromData(char **data) | |
{ | |
ImageNode *result = NULL; | |
#ifdef USE_XPM | |
XpmAttributes attr; | |
XImage *image; | |
XImage *shape; | |
int rc; | |
Assert(data); | |
attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure; | |
attr.alloc_color = AllocateColor; | |
attr.free_colors = FreeColors; | |
attr.color_closure = NULL; | |
rc = XpmCreateImageFromData(display, data, &image, &shape, &attr); | |
if(rc == XpmSuccess) { | |
result = CreateImageFromXImages(image, shape); | |
JXDestroyImage(image); | |
if(shape) { | |
JXDestroyImage(shape); | |
} | |
} | |
#endif | |
return result; | |
} | |
/** Load an image from a pixmap. */ | |
#ifdef USE_ICONS | |
ImageNode *LoadImageFromDrawable(Drawable pmap, Pixmap mask) | |
{ | |
ImageNode *result = NULL; | |
XImage *mask_image = NULL; | |
XImage *icon_image = NULL; | |
Window rwindow; | |
int x, y; | |
unsigned int width, height; | |
unsigned int border_width; | |
unsigned int depth; | |
JXGetGeometry(display, pmap, &rwindow, &x, &y, &width, &height, | |
&border_width, &depth); | |
icon_image = JXGetImage(display, pmap, 0, 0, width, height, | |
AllPlanes, ZPixmap); | |
if(mask != None) { | |
mask_image = JXGetImage(display, mask, 0, 0, width, height, 1, ZPixmap); | |
} | |
result = CreateImageFromXImages(icon_image, mask_image); | |
if(icon_image) { | |
JXDestroyImage(icon_image); | |
} | |
if(mask_image) { | |
JXDestroyImage(mask_image); | |
} | |
return result; | |
} | |
#endif | |
/** Load a PNG image from the given file name. | |
* Since libpng uses longjmp, this function is not reentrant to simplify | |
* the issues surrounding longjmp and local variables. | |
*/ | |
#ifdef USE_PNG | |
ImageNode *LoadPNGImage(const char *fileName) | |
{ | |
static ImageNode *result; | |
static FILE *fd; | |
static unsigned char **rows; | |
static png_structp pngData; | |
static png_infop pngInfo; | |
static png_infop pngEndInfo; | |
unsigned char header[8]; | |
unsigned long rowBytes; | |
int bitDepth, colorType; | |
unsigned int x, y; | |
png_uint_32 width; | |
png_uint_32 height; | |
Assert(fileName); | |
result = NULL; | |
fd = NULL; | |
rows = NULL; | |
pngData = NULL; | |
pngInfo = NULL; | |
pngEndInfo = NULL; | |
fd = fopen(fileName, "rb"); | |
if(!fd) { | |
return NULL; | |
} | |
x = fread(header, 1, sizeof(header), fd); | |
if(x != sizeof(header) || png_sig_cmp(header, 0, sizeof(header))) { | |
fclose(fd); | |
return NULL; | |
} | |
pngData = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
if(JUNLIKELY(!pngData)) { | |
fclose(fd); | |
Warning(_("could not create read struct for PNG image: %s"), fileName); | |
return NULL; | |
} | |
if(JUNLIKELY(setjmp(png_jmpbuf(pngData)))) { | |
png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo); | |
if(fd) { | |
fclose(fd); | |
} | |
if(rows) { | |
ReleaseStack(rows); | |
} | |
DestroyImage(result); | |
Warning(_("error reading PNG image: %s"), fileName); | |
return NULL; | |
} | |
pngInfo = png_create_info_struct(pngData); | |
if(JUNLIKELY(!pngInfo)) { | |
png_destroy_read_struct(&pngData, NULL, NULL); | |
fclose(fd); | |
Warning(_("could not create info struct for PNG image: %s"), fileName); | |
return NULL; | |
} | |
pngEndInfo = png_create_info_struct(pngData); | |
if(JUNLIKELY(!pngEndInfo)) { | |
png_destroy_read_struct(&pngData, &pngInfo, NULL); | |
fclose(fd); | |
Warning("could not create end info struct for PNG image: %s", fileName); | |
return NULL; | |
} | |
png_init_io(pngData, fd); | |
png_set_sig_bytes(pngData, sizeof(header)); | |
png_read_info(pngData, pngInfo); | |
png_get_IHDR(pngData, pngInfo, &width, &height, | |
&bitDepth, &colorType, NULL, NULL, NULL); | |
result = CreateImage(width, height, 0); | |
png_set_expand(pngData); | |
if(bitDepth == 16) { | |
png_set_strip_16(pngData); | |
} else if(bitDepth < 8) { | |
png_set_packing(pngData); | |
} | |
png_set_swap_alpha(pngData); | |
png_set_filler(pngData, 0xFF, PNG_FILLER_BEFORE); | |
if(colorType == PNG_COLOR_TYPE_GRAY | |
|| colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { | |
png_set_gray_to_rgb(pngData); | |
} | |
png_read_update_info(pngData, pngInfo); | |
rowBytes = png_get_rowbytes(pngData, pngInfo); | |
rows = AllocateStack(result->height * sizeof(result->data)); | |
y = 0; | |
for(x = 0; x < result->height; x++) { | |
rows[x] = &result->data[y]; | |
y += rowBytes; | |
} | |
png_read_image(pngData, rows); | |
png_read_end(pngData, pngInfo); | |
png_destroy_read_struct(&pngData, &pngInfo, &pngEndInfo); | |
fclose(fd); | |
ReleaseStack(rows); | |
rows = NULL; | |
return result; | |
} | |
#endif /* USE_PNG */ | |
/** Load a JPEG image from the specified file. */ | |
#ifdef USE_JPEG | |
typedef struct { | |
struct jpeg_error_mgr pub; | |
jmp_buf jbuffer; | |
} JPEGErrorStruct; | |
static void JPEGErrorHandler(j_common_ptr cinfo) { | |
JPEGErrorStruct *es = (JPEGErrorStruct*)cinfo->err; | |
longjmp(es->jbuffer, 1); | |
} | |
ImageNode *LoadJPEGImage(const char *fileName) | |
{ | |
static ImageNode *result; | |
static struct jpeg_decompress_struct cinfo; | |
static FILE *fd; | |
static JSAMPARRAY buffer; | |
static JPEGErrorStruct jerr; | |
int rowStride; | |
int x; | |
int inIndex, outIndex; | |
/* Open the file. */ | |
fd = fopen(fileName, "rb"); | |
if(fd == NULL) { | |
return NULL; | |
} | |
/* Make sure everything is initialized so we can recover from errors. */ | |
result = NULL; | |
buffer = NULL; | |
/* Setup the error handler. */ | |
cinfo.err = jpeg_std_error(&jerr.pub); | |
jerr.pub.error_exit = JPEGErrorHandler; | |
/* Control will return here if an error was encountered. */ | |
if(setjmp(jerr.jbuffer)) { | |
DestroyImage(result); | |
jpeg_destroy_decompress(&cinfo); | |
fclose(fd); | |
return NULL; | |
} | |
/* Prepare to load the file. */ | |
jpeg_create_decompress(&cinfo); | |
jpeg_stdio_src(&cinfo, fd); | |
/* Check the header. */ | |
jpeg_read_header(&cinfo, TRUE); | |
/* Start decompression. */ | |
jpeg_start_decompress(&cinfo); | |
rowStride = cinfo.output_width * cinfo.output_components; | |
buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, | |
JPOOL_IMAGE, rowStride, 1); | |
result = CreateImage(cinfo.image_width, cinfo.image_height, 0); | |
result->data = Allocate(4 * result->width * result->height); | |
/* Read lines. */ | |
outIndex = 0; | |
while(cinfo.output_scanline < cinfo.output_height) { | |
jpeg_read_scanlines(&cinfo, buffer, 1); | |
inIndex = 0; | |
for(x = 0; x < result->width; x++) { | |
switch(cinfo.output_components) { | |
case 1: /* Grayscale. */ | |
result->data[outIndex + 1] = GETJSAMPLE(buffer[0][inIndex]); | |
result->data[outIndex + 2] = GETJSAMPLE(buffer[0][inIndex]); | |
result->data[outIndex + 3] = GETJSAMPLE(buffer[0][inIndex]); | |
inIndex += 1; | |
break; | |
default: /* RGB */ | |
result->data[outIndex + 1] = GETJSAMPLE(buffer[0][inIndex + 0]); | |
result->data[outIndex + 2] = GETJSAMPLE(buffer[0][inIndex + 1]); | |
result->data[outIndex + 3] = GETJSAMPLE(buffer[0][inIndex + 2]); | |
inIndex += 3; | |
break; | |
} | |
result->data[outIndex + 0] = 0xFF; | |
outIndex += 4; | |
} | |
} | |
/* Clean up. */ | |
jpeg_destroy_decompress(&cinfo); | |
fclose(fd); | |
return result; | |
} | |
#endif /* USE_JPEG */ | |
#ifdef USE_RSVG | |
ImageNode *LoadSVGImage(const char *fileName) | |
{ | |
#if !GLIB_CHECK_VERSION(2, 35, 0) | |
static char initialized = 0; | |
#endif | |
ImageNode *result = NULL; | |
RsvgHandle *rh; | |
RsvgDimensionData dim; | |
GError *e; | |
cairo_surface_t *target; | |
cairo_t *context; | |
int stride; | |
int i; | |
Assert(fileName); | |
#if !GLIB_CHECK_VERSION(2, 35, 0) | |
if(!initialized) { | |
initialized = 1; | |
g_type_init(); | |
} | |
#endif | |
/* Load the image from the file. */ | |
e = NULL; | |
rh = rsvg_handle_new_from_file(fileName, &e); | |
if(!rh) { | |
return NULL; | |
} | |
rsvg_handle_get_dimensions(rh, &dim); | |
result = CreateImage(dim.width, dim.height, 0); | |
memset(result->data, 0, dim.width * dim.height * 4); | |
/* Create the target surface. */ | |
stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, dim.width); | |
target = cairo_image_surface_create_for_data(result->data, | |
CAIRO_FORMAT_ARGB32, | |
dim.width, dim.height, stride); | |
context = cairo_create(target); | |
cairo_paint_with_alpha(context, 0.0); | |
rsvg_handle_render_cairo(rh, context); | |
cairo_destroy(context); | |
cairo_surface_destroy(target); | |
g_object_unref(rh); | |
for(i = 0; i < 4 * dim.width * dim.height; i += 4) { | |
const unsigned int temp = *(unsigned int*)&result->data[i]; | |
const unsigned int alpha = (temp >> 24) & 0xFF; | |
const unsigned int red = (temp >> 16) & 0xFF; | |
const unsigned int green = (temp >> 8) & 0xFF; | |
const unsigned int blue = (temp >> 0) & 0xFF; | |
result->data[i + 0] = alpha; | |
if(alpha > 0) { | |
result->data[i + 1] = (red * 255) / alpha; | |
result->data[i + 2] = (green * 255) / alpha; | |
result->data[i + 3] = (blue * 255) / alpha; | |
} | |
} | |
return result; | |
} | |
#elif defined USE_NANOSVG | |
ImageNode *LoadSVGImage(const char *fileName){ | |
NSVGimage *shapes = nsvgParseFromFile(fileName, "px", 96.0f); | |
if (shapes) { | |
int w = shapes->width, h=shapes->height; | |
ImageNode *image = CreateImage(w,h,0); | |
NSVGrasterizer *rast = nsvgCreateRasterizer(); | |
nsvgRasterize(rast, shapes, 0,0,1, image->data, w, h, w*4); | |
if (image->data) { | |
unsigned *dp = (unsigned *)image->data; | |
size_t i, len = image->width*image->height; | |
for(i=0;i<len;i++) | |
dp[i]= (dp[i]<<8)|(dp[i]>>24); | |
return image; | |
} | |
} | |
} | |
#endif /* USE_RSVG */ | |
#ifdef USE_STB_IMAGE | |
ImageNode *LoadstbImage(const char *fileName){ | |
int n=4; /* depth in bytes 4 = 32bpp */ | |
ImageNode *image = CreateImage(0,0,0); | |
image->data = | |
stbi_load(fileName, &image->width, &image->height, &n, 4); | |
if (image->data) { | |
unsigned *dp = (unsigned *)image->data; | |
size_t i, len = image->width*image->height; | |
for(i=0;i<len;i++) | |
dp[i]= (dp[i]<<8)|(dp[i]>>24); | |
return image; | |
} | |
} | |
#endif | |
/** Load an XPM image from the specified file. */ | |
#ifdef USE_XPM | |
ImageNode *LoadXPMImage(const char *fileName) | |
{ | |
ImageNode *result = NULL; | |
XpmAttributes attr; | |
XImage *image; | |
XImage *shape; | |
int rc; | |
Assert(fileName); | |
attr.valuemask = XpmAllocColor | XpmFreeColors | XpmColorClosure; | |
attr.alloc_color = AllocateColor; | |
attr.free_colors = FreeColors; | |
attr.color_closure = NULL; | |
rc = XpmReadFileToImage(display, (char*)fileName, &image, &shape, &attr); | |
if(rc == XpmSuccess) { | |
result = CreateImageFromXImages(image, shape); | |
JXDestroyImage(image); | |
if(shape) { | |
JXDestroyImage(shape); | |
} | |
} | |
return result; | |
} | |
#endif /* USE_XPM */ | |
/** Load an XBM image from the specified file. */ | |
#ifdef USE_XBM | |
ImageNode *LoadXBMImage(const char *fileName) | |
{ | |
ImageNode *result = CreateImage(0,0,1); | |
int dummy1, dummy2, rc; | |
rc = XReadBitmapFileData(fileName,&result->width,&result->height, | |
&(result->data),&dummy1,&dummy2); | |
return (rc == BitmapSuccess)?result:NULL; | |
return result; | |
} | |
#endif /* USE_XBM */ | |
/** Create an image from XImages giving color and shape information. */ | |
#ifdef USE_ICONS | |
ImageNode *CreateImageFromXImages(XImage *image, XImage *shape) | |
{ | |
ImageNode *result; | |
unsigned char *dest; | |
int x, y; | |
result = CreateImage(image->width, image->height, 0); | |
dest = result->data; | |
for(y = 0; y < image->height; y++) { | |
for(x = 0; x < image->width; x++) { | |
XColor color; | |
if(!shape || XGetPixel(shape, x, y)) { | |
*dest++ = 255; | |
} else { | |
*dest++ = 0; | |
} | |
color.pixel = XGetPixel(image, x, y); | |
if(image->depth == 1) { | |
const unsigned char value = color.pixel ? 0 : 255; | |
*dest++ = value; | |
*dest++ = value; | |
*dest++ = value; | |
} else { | |
GetColorFromIndex(&color); | |
*dest++ = (unsigned char)(color.red >> 8); | |
*dest++ = (unsigned char)(color.green >> 8); | |
*dest++ = (unsigned char)(color.blue >> 8); | |
} | |
} | |
} | |
return result; | |
} | |
#endif /* USE_ICONS */ | |
ImageNode *CreateImage(unsigned int width, unsigned int height, char bitmap) | |
{ | |
unsigned int image_size; | |
if(bitmap) { | |
image_size = (width * height + 7) / 8; | |
} else { | |
image_size = 4 * width * height; | |
} | |
ImageNode *image = Allocate(sizeof(ImageNode)); | |
image->data = Allocate(image_size); | |
image->next = NULL; | |
image->nodes = NULL; | |
image->bitmap = bitmap; | |
image->width = width; | |
image->height = height; | |
return image; | |
} | |
/** Destroy an image node. */ | |
void DestroyImage(ImageNode *image) { | |
while(image) { | |
ImageNode *next = image->next; | |
if(image->data) { | |
Release(image->data); | |
} | |
Release(image); | |
image = next; | |
} | |
} | |
/** Callback to allocate a color for libxpm. */ | |
#ifdef USE_XPM | |
int AllocateColor(Display *d, Colormap cmap, char *name, | |
XColor *c, void *closure) | |
{ | |
if(name) { | |
if(!JXParseColor(d, cmap, name, c)) { | |
return -1; | |
} | |
} | |
GetColorIndex(c); | |
return 1; | |
} | |
#endif /* USE_XPM */ | |
/** Callback to free colors allocated by libxpm. | |
* We don't need to do anything here since color.c takes care of this. | |
*/ | |
#ifdef USE_XPM | |
int FreeColors(Display *d, Colormap cmap, Pixel *pixels, int n, | |
void *closure) | |
{ | |
return 1; | |
} | |
#endif /* USE_XPM */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment