efl/legacy/evas/src/modules/loaders/psd/evas_image_load_psd.c

977 lines
24 KiB
C

#define _XOPEN_SOURCE
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_EVIL
# include <Evil.h>
#endif
#include "evas_common.h"
#include "evas_private.h"
typedef struct _PSD_Header PSD_Header;
typedef enum _PSD_Mode
{
PSD_GREYSCALE = 1,
PSD_INDEXED = 2,
PSD_RGB = 3,
PSD_CMYK = 4
} PSD_Mode;
struct _PSD_Header
{
unsigned char signature[4];
unsigned short version;
unsigned char reserved[9];
unsigned short channels;
unsigned int height;
unsigned int width;
unsigned short depth;
unsigned short channel_num;
PSD_Mode mode;
};
enum {
READ_COMPRESSED_SUCCESS,
READ_COMPRESSED_ERROR_FILE_CORRUPT,
READ_COMPRESSED_ERROR_FILE_READ_ERROR
};
static Eina_Bool get_compressed_channels_length(PSD_Header *Head,
FILE *file,
unsigned short *rle_table,
unsigned int *chanlen);
static int
read_ushort(FILE *file, unsigned short *ret)
{
unsigned char b[2];
if (fread(b, sizeof(unsigned char), 2, file) != 2) return 0;
// FIXME: need to check order
*ret = (b[0] << 8) | b[1];
return 1;
}
static int
read_uint(FILE *file, unsigned int *ret)
{
unsigned char b[4];
if (fread(b, sizeof(unsigned char), 4, file) != 4) return 0;
// FIXME: need to check order
*ret = ARGB_JOIN(b[0], b[1], b[2], b[3]);
return 1;
}
// Internal function used to get the Psd header from the current file.
Eina_Bool
psd_get_header(PSD_Header *header, FILE * file)
{
unsigned short tmp;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
CHECK_RET(fread(header->signature, sizeof (unsigned char), 4, file), 4);
CHECK_RET(read_ushort(file, &header->version), 1);
CHECK_RET(fread(header->reserved, sizeof (unsigned char), 6, file), 6);
CHECK_RET(read_ushort(file, &header->channels), 1);
CHECK_RET(read_uint(file, &header->height), 1);
CHECK_RET(read_uint(file, &header->width), 1);
CHECK_RET(read_ushort(file, &header->depth), 1);
CHECK_RET(read_ushort(file, &tmp), 1);
header->mode = tmp;
#undef CHECK_RET
/* fprintf(stderr, "<<<<<<<<<<<\nsignature : %c%c%c%c\n", */
/* header->signature[0], */
/* header->signature[1], */
/* header->signature[2], */
/* header->signature[3]); */
/* fprintf(stderr, "version : %i\n", header->version); */
/* fprintf(stderr, "channels : %i\n", header->channels); */
/* fprintf(stderr, "width x height : %dx%d\n", header->width, header->height); */
/* fprintf(stderr, "depth : %i\n", header->depth); */
/* fprintf(stderr, "mode : %i\n>>>>>>>>>>>>\n", header->mode); */
return EINA_TRUE;
}
// Internal function used to check if the HEADER is a valid Psd header.
Eina_Bool
is_psd(PSD_Header *header)
{
if (strncmp((char*)header->signature, "8BPS", 4))
return EINA_FALSE;
if (header->version != 1)
return EINA_FALSE;
if (header->channels < 1 || header->channels > 24)
return EINA_FALSE;
if (header->height < 1 || header->width < 1)
return EINA_FALSE;
if (header->depth != 1 && header->depth != 8 && header->depth != 16)
return EINA_FALSE;
return EINA_TRUE;
}
static Eina_Bool
evas_image_load_file_head_psd(Image_Entry *ie, const char *FileName,
const char *key __UNUSED__, int *error)
{
FILE *f;
PSD_Header header;
Eina_Bool correct;
*error = EVAS_LOAD_ERROR_NONE;
f = fopen(FileName, "rb");
if (f == NULL)
{
*error = EVAS_LOAD_ERROR_DOES_NOT_EXIST;
return EINA_FALSE;
}
correct = psd_get_header(&header, f);
fclose(f);
if (!correct || !is_psd(&header))
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
ie->w = header.width;
ie->h = header.height;
if (header.channels == 3) ie->flags.alpha = 0;
else ie->flags.alpha = 1;
return EINA_TRUE;
}
static unsigned int
read_compressed_channel(FILE* file,
const unsigned int channel_length __UNUSED__,
unsigned int size,
unsigned char* channel)
{
// FIXME: what does channel_length means, and why is it not used
unsigned int i;
char headbyte, c;
#define CHECK_RET(Call, Value) \
if (Call != Value) return READ_COMPRESSED_ERROR_FILE_READ_ERROR;
for (i = 0; i < size; )
{
CHECK_RET(fread(&headbyte, 1, 1, file), 1);
if (headbyte >= 0)
{
if (i + headbyte > size)
return READ_COMPRESSED_ERROR_FILE_CORRUPT;
CHECK_RET(fread(channel + i, headbyte + 1, 1, file), 1);
i += headbyte + 1;
}
else if (headbyte >= -127 && headbyte <= -1)
{
int run;
CHECK_RET(fread(&c, 1, 1, file), 1);
run = c;
/* if (run == -1) */
/* return READ_COMPRESSED_ERROR_FILE_READ_ERROR; */
if (i + (-headbyte + 1) > size)
return READ_COMPRESSED_ERROR_FILE_CORRUPT;
memset(channel + i, run, -headbyte + 1);
i += -headbyte + 1;
}
}
#undef CHECK_RET
return READ_COMPRESSED_SUCCESS;
}
Eina_Bool
psd_get_data(Image_Entry *ie __UNUSED__,
PSD_Header *head,
FILE *f,
unsigned char *buffer, Eina_Bool compressed,
int *error)
{
unsigned int c, x, y, numchan, bps, bpc, bpp;
unsigned int pixels_count;
unsigned char *channel = NULL;
unsigned char *data = NULL;
// Added 01-07-2009: This is needed to correctly load greyscale and
// paletted images.
switch (head->mode)
{
case PSD_GREYSCALE:
case PSD_INDEXED:
numchan = 1;
break;
default:
numchan = 3;
}
bpp = head->channels;
bpc = head->depth / 8;
pixels_count = head->width * head->height;
data = malloc(sizeof (unsigned char) * pixels_count * bpp);
if (!data) return EINA_FALSE;
channel = malloc(sizeof (unsigned char) * pixels_count * bpc);
if (!channel)
{
free(data);
return EINA_FALSE;
}
bps = head->width * head->channels * bpc;
// @TODO: Add support for this in, though I have yet to run across a .psd
// file that uses this.
if (compressed && bpc == 2)
{
free(data);
free(channel);
fprintf(stderr, "unsupported file format.\n");
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
#define CHECK_RET(Call, Value) \
if (Call != Value) \
{ \
free(data); \
free(channel); \
return EINA_FALSE; \
}
if (!compressed)
{
if (bpc == 1)
{
for (c = 0; c < numchan; c++)
{
unsigned char *tmp = channel;
CHECK_RET(fread(tmp, pixels_count, 1, f), 1);
for (y = 0; y < head->height * bps; y += bps)
{
for (x = 0; x < bps; x += bpp, tmp++)
{
data[y + x + c] = *tmp;
}
}
}
// Accumulate any remaining channels into a single alpha channel
//@TODO: This needs to be changed for greyscale images.
for (; c < head->channels; c++)
{
unsigned char *tmp = channel;
CHECK_RET(fread(channel, pixels_count, 1, f), 1);
for (y = 0; y < head->height * bps; y += bps)
{
for (x = 0; x < bps; x += bpp, tmp++)
{
unsigned short newval;
// previous formula was : (old / 255 * new / 255) * 255
newval = (*tmp) * data[y + x + 3];
data[y + x + 3] = newval >> 8;
}
}
}
}
else
{
int bps2;
bps2 = bps / 2;
// iCurImage->Bpc == 2
for (c = 0; c < numchan; c++)
{
unsigned short *shortptr = (unsigned short*) channel;
CHECK_RET(fread(channel, pixels_count * 2, 1, f), 1);
for (y = 0; y < head->height * bps2; y += bps2)
{
for (x = 0; x < (unsigned int)bps2; x += bpp, shortptr++)
{
((unsigned short*)data)[y + x + c] = *shortptr;
}
}
}
// Accumulate any remaining channels into a single alpha channel
//@TODO: This needs to be changed for greyscale images.
for (; c < head->channels; c++)
{
unsigned short *shortptr = (unsigned short*) channel;
CHECK_RET(fread(channel, pixels_count * 2, 1, f), 1);
for (y = 0; y < head->height * bps2; y += bps2)
{
for (x = 0; x < (unsigned int)bps2; x += bpp, shortptr)
{
unsigned int newval;
newval = *shortptr * ((unsigned short*)data)[y + x + 3];
((unsigned short*)data)[y + x + 3] = newval >> 16;
}
}
}
}
}
else
{
unsigned short *rle_table;
unsigned int *chanlen;
rle_table = alloca(head->height * head->channel_num * sizeof (unsigned short));
chanlen = alloca(head->channel_num * sizeof (unsigned int));
if (!get_compressed_channels_length(head, f, rle_table, chanlen))
goto file_read_error;
for (c = 0; c < numchan; c++)
{
unsigned char *tmp = channel;
int err;
err = read_compressed_channel(f,
chanlen[c],
pixels_count,
channel);
if (err == READ_COMPRESSED_ERROR_FILE_CORRUPT)
goto file_corrupt;
else if (err == READ_COMPRESSED_ERROR_FILE_READ_ERROR)
goto file_read_error;
for (y = 0; y < head->height * bps; y += bps)
{
for (x = 0; x < bps; x += bpp, tmp++)
{
data[y + x + c] = *tmp;
}
}
}
// Initialize the alpha channel to solid
//@TODO: This needs to be changed for greyscale images.
if (head->channels >= 4)
{
for (y = 0; y < head->height * bps; y += bps)
{
for (x = 0; x < bps; x += bpp)
{
data[y + x + 3] = 255;
}
}
for (; c < head->channels; c++)
{
unsigned char *tmp = channel;
int err;
err = read_compressed_channel(f,
chanlen[c],
pixels_count,
channel);
if (err == READ_COMPRESSED_ERROR_FILE_CORRUPT)
goto file_corrupt;
else if (err == READ_COMPRESSED_ERROR_FILE_READ_ERROR)
goto file_read_error;
for (y = 0; y < head->height * bps; y += bps)
{
for (x = 0; x < bps; x += bpp, tmp++)
{
unsigned short newval;
newval = *tmp * data[y + x + 3];
data[y + x + 3] = newval >> 8;
}
}
}
}
}
if (bpp == 3)
{
for (x = 0; x < pixels_count; x++)
{
buffer[x * 4 + 0] = data[x * 3 + 2];
buffer[x * 4 + 1] = data[x * 3 + 1];
buffer[x * 4 + 2] = data[x * 3 + 0];
buffer[x * 4 + 3] = 255;
}
}
else
{
// BRGA to RGBA
for (x= 0; x < pixels_count; x++)
{
buffer[x * 4 + 0] = data[x * 4 + 2];
buffer[x * 4 + 1] = data[x * 4 + 1];
buffer[x * 4 + 2] = data[x * 4 + 0];
buffer[x * 4 + 3] = data[x * 4 + 3];
}
}
free(channel);
free(data);
return EINA_TRUE;
#undef CHECK_RET
file_corrupt:
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
file_read_error:
free(channel);
free(data);
return EINA_FALSE;
}
Eina_Bool
get_single_channel(Image_Entry *ie __UNUSED__,
PSD_Header *head,
FILE *f,
unsigned char *buffer,
Eina_Bool compressed)
{
unsigned int i, bpc;
unsigned short *tmp;
char headbyte;
int c;
int pixels_count;
tmp = (unsigned short*)buffer;
bpc = (head->depth / 8);
pixels_count = head->width * head->height;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
if (!compressed)
{
if (bpc == 1)
{
CHECK_RET(fread(buffer, pixels_count, 1, f), 1);
}
else
{ // Bpc == 2
CHECK_RET(fread(buffer, pixels_count * 2, 1, f), 1);
}
}
else
{
for (i = 0; i < (unsigned int)pixels_count; )
{
CHECK_RET(fread(&headbyte, 1, 1, f), 1);
if (headbyte >= 0)
{ // && HeadByte <= 127
CHECK_RET(fread(buffer + i, headbyte + 1, 1, f), 1);
i += headbyte + 1;
}
if (headbyte >= -127 && headbyte <= -1)
{
int run;
CHECK_RET(fread(&c, 1, 1, f), 1);
run = c;
if (run == -1) return EINA_FALSE;
memset(buffer + i, run, -headbyte + 1);
i += -headbyte + 1;
}
}
}
#undef CHECK_RET
return EINA_TRUE;
}
Eina_Bool
read_psd_grey(Image_Entry *ie, PSD_Header *head, FILE * f, int *error)
{
unsigned int color_mode, resource_size, misc_info;
unsigned short compressed;
unsigned int type;
void *surface = NULL;
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
CHECK_RET(read_uint(f, &color_mode), 1);
// Skip over the 'color mode data section'
CHECK_RET(fseek(f, color_mode, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &resource_size), 1);
// Read the 'image resources section'
CHECK_RET(fseek(f, resource_size, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &misc_info), 1);
CHECK_RET(fseek(f, misc_info, SEEK_CUR), 0);
CHECK_RET(read_ushort(f, &compressed), 1);
ie->w = head->width;
ie->h = head->height;
if (head->channels == 3) ie->flags.alpha = 0;
else ie->flags.alpha = 1;
head->channel_num = head->channels;
// Temporary to read only one channel...some greyscale .psd files have 2.
head->channels = 1;
switch (head->depth)
{
case 8:
type = 1;
break;
case 16:
type = 2;
break;
default:
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
evas_cache_image_surface_alloc(ie, ie->w, ie->h);
surface = evas_cache_image_pixels(ie);
if (!surface)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto cleanup_error;
}
if (!psd_get_data(ie, head, f, surface, compressed, error))
goto cleanup_error;
return EINA_TRUE;
#undef CHECK_RET
cleanup_error:
return EINA_FALSE;
}
Eina_Bool
read_psd_indexed(Image_Entry *ie, PSD_Header *head, FILE * f, int *error)
{
unsigned int color_mode, resource_size, misc_info;
unsigned short compressed;
void *surface;
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
CHECK_RET(read_uint(f, &color_mode), 1);
CHECK_RET((color_mode % 3), 0);
/*
Palette = (unsigned char*)malloc(Colormode);
if (Palette == NULL)
return EINA_FALSE;
if (fread(&Palette, 1, Colormode, file) != Colormode)
goto cleanup_error;
*/
// Skip over the 'color mode data section'
CHECK_RET(fseek(f, color_mode, SEEK_CUR), 0);
// Read the 'image resources section'
CHECK_RET(read_uint(f, &resource_size), 1);
CHECK_RET(fseek(f, resource_size, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &misc_info), 1);
CHECK_RET(fseek(f, misc_info, SEEK_CUR), 0);
CHECK_RET(read_ushort(f, &compressed), 1);
if (head->channels != 1 || head->depth != 8)
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
head->channel_num = head->channels;
ie->w = head->width;
ie->h = head->height;
if (head->channels == 3) ie->flags.alpha = 0;
else ie->flags.alpha = 1;
evas_cache_image_surface_alloc(ie, ie->w, ie->h);
surface = evas_cache_image_pixels(ie);
if (!surface)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return EINA_FALSE;
}
if (!psd_get_data(ie, head, f, surface, compressed, error))
return EINA_FALSE;
return EINA_TRUE;
#undef CHECK_RET
}
Eina_Bool
read_psd_rgb(Image_Entry *ie, PSD_Header *head, FILE *f, int *error)
{
unsigned int color_mode, resource_size, misc_info;
unsigned short compressed;
unsigned int type;
void *surface;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
CHECK_RET(read_uint(f, &color_mode), 1);
// Skip over the 'color mode data section'
CHECK_RET(fseek(f, color_mode, SEEK_CUR), 0);
// Read the 'image resources section'
CHECK_RET(read_uint(f, &resource_size), 1);
CHECK_RET(fseek(f, resource_size, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &misc_info), 1);
CHECK_RET(fseek(f, misc_info, SEEK_CUR), 0);
CHECK_RET(read_ushort(f, &compressed), 1);
head->channel_num = head->channels;
switch (head->depth)
{
case 8:
type = 1;
break;
case 16:
type = 2;
break;
default:
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
ie->w = head->width;
ie->h = head->height;
if (head->channels == 3) ie->flags.alpha = 0;
else ie->flags.alpha = 1;
evas_cache_image_surface_alloc(ie, ie->w, ie->h);
surface = evas_cache_image_pixels(ie);
if (!surface)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto cleanup_error;
}
if (!psd_get_data(ie, head, f, surface, compressed, error))
goto cleanup_error;
evas_common_image_premul(ie);
return EINA_TRUE;
#undef CHECK_RET
cleanup_error:
return EINA_FALSE;
}
Eina_Bool
read_psd_cmyk(Image_Entry *ie, PSD_Header *head, FILE *f, int *error)
{
unsigned int color_mode, resource_size, misc_info, size, i, j, data_size;
unsigned short compressed;
unsigned int format, type;
unsigned char *kchannel = NULL;
void *surface;
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
#define CHECK_RET(Call, Value) \
if (Call != Value) return EINA_FALSE;
CHECK_RET(read_uint(f, &color_mode), 1);
// Skip over the 'color mode data section'
CHECK_RET(fseek(f, color_mode, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &resource_size), 1);
// Read the 'image resources section'
CHECK_RET(fseek(f, resource_size, SEEK_CUR), 0);
CHECK_RET(read_uint(f, &misc_info), 1);
CHECK_RET(fseek(f, misc_info, SEEK_CUR), 0);
CHECK_RET(read_ushort(f, &compressed), 1);
switch (head->channels)
{
case 4:
format = 0x1907;
head->channel_num = 4;
head->channels = 3;
break;
case 5:
format = 0x1908;
head->channel_num = 5;
head->channels = 4;
break;
default:
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
switch (head->depth)
{
case 8:
type = 1;
break;
case 16:
type = 2;
break;
default:
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
return EINA_FALSE;
}
ie->w = head->width;
ie->h = head->height;
if (head->channels == 3) ie->flags.alpha = 0;
else ie->flags.alpha = 1;
evas_cache_image_surface_alloc(ie, ie->w, ie->h);
surface = evas_cache_image_pixels(ie);
if (!surface)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto cleanup_error;
}
if (!psd_get_data(ie, head, f, surface, compressed, error))
goto cleanup_error;
size = type * ie->w * ie->h;
kchannel = malloc(size);
if (kchannel == NULL)
goto cleanup_error;
if (!get_single_channel(ie, head, f, kchannel, compressed))
goto cleanup_error;
data_size = head->channels * type * ie->w * ie->h;
if (format == 0x1907)
{
unsigned char *tmp = surface;
const unsigned char *limit = tmp + data_size;
for (i = 0, j = 0; tmp < limit; tmp++, j++)
{
int k;
for (k = 0; k < 3; k++)
*tmp = (*tmp * kchannel[j]) >> 8;
// FIXME: tmp[i+3] = 255;
}
}
else
{ // RGBA
unsigned char *tmp = surface;
const unsigned char *limit = tmp + data_size;
// The KChannel array really holds the alpha channel on this one.
for (i = 0, j = 0; tmp < limit; tmp += 4, j++)
{
tmp[0] = (tmp[0] * tmp[3]) >> 8;
tmp[1] = (tmp[1] * tmp[3]) >> 8;
tmp[2] = (tmp[2] * tmp[3]) >> 8;
tmp[3] = kchannel[j]; // Swap 'K' with alpha channel.
}
}
free(kchannel);
evas_common_image_premul(ie);
return EINA_TRUE;
cleanup_error:
free(kchannel);
return EINA_FALSE;
}
static Eina_Bool
evas_image_load_file_data_psd(Image_Entry *ie,
const char *file,
const char *key __UNUSED__,
int *error)
{
FILE *f;
PSD_Header header;
Eina_Bool bpsd = EINA_FALSE;
f = fopen(file, "rb");
if (f == NULL)
{
*error = EVAS_LOAD_ERROR_DOES_NOT_EXIST;
return bpsd;
}
if (!psd_get_header(&header, f) || !is_psd(&header))
{
fclose(f);
*error = EVAS_LOAD_ERROR_GENERIC;
return EINA_FALSE;
}
ie->w = header.width;
ie->h = header.height;
*error = EVAS_LOAD_ERROR_NONE;
switch (header.mode)
{
case PSD_GREYSCALE: // Greyscale
bpsd = read_psd_grey(ie, &header, f, error);
break;
case PSD_INDEXED: // Indexed
bpsd = read_psd_indexed(ie, &header, f, error);
break;
case PSD_RGB: // RGB
bpsd = read_psd_rgb(ie, &header, f, error);
break;
case PSD_CMYK: // CMYK
bpsd = read_psd_cmyk(ie, &header, f, error);
break;
default :
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
bpsd = EINA_FALSE;
}
fclose(f);
return bpsd;
}
static Eina_Bool
get_compressed_channels_length(PSD_Header *head,
FILE * file,
unsigned short *rle_table,
unsigned int *chanlen)
{
unsigned int j;
unsigned int c;
if (fread(rle_table,
sizeof(unsigned short),
head->height * head->channel_num,
file) != head->height * head->channel_num)
return EINA_FALSE;
memset(chanlen, 0, head->channel_num * sizeof(unsigned int));
for (c = 0; c < head->channel_num; c++)
{
unsigned int i;
j = c * head->height;
for (i = 0; i < head->height; i++)
{
chanlen[c] += rle_table[i + j];
}
}
return EINA_TRUE;
}
static const Evas_Image_Load_Func evas_image_load_psd_func = {
EINA_TRUE,
evas_image_load_file_head_psd,
evas_image_load_file_data_psd,
NULL
};
static int
module_open(Evas_Module *em)
{
if (!em) return 0;
em->functions = (void *)(&evas_image_load_psd_func);
return 1;
}
static void
module_close(Evas_Module *em __UNUSED__)
{
}
static Evas_Module_Api evas_modapi =
{
EVAS_MODULE_API_VERSION,
"psd",
"none",
{
module_open,
module_close
}
};
EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, psd);
#ifndef EVAS_STATIC_BUILD_PSD
EVAS_EINA_MODULE_DEFINE(image_loader, psd);
#endif