The mi_img_custom_format function allows registering up to 16 new file formats, in addition to the file formats already built into raylib such as jpg or tif. This requires installing six functions in raylib (test, create, open, close, read, and write the file) and passing a number of flags. This example defines a new file format foo, which is both the name of the format and the standard file extension (however, it will work with any extension, the new name merely establishes the naming convention).
The format written in this example is Jef Poskanzer's Portable Pixmap format type P6. Reading additionally supports type P3. The standard name of this format is ppm, but raylib already supports this type so a new name had to be chosen for this example.
The following code can be compiled into a shared library (DSO or DLL) and linked into raylib with mi_link_file_add, or with a link statement in the master host's rayrc startup file. For this reason the name of the "mainline" function is module_init - raylib will call this function on loading the library, so no explicit call is needed. However, normally this code will reside in the client application that raylib is linked into, not a separate library, and mi_img_custom_format may be called anywhere after initialization of raylib.
This step defines a data structure that maintains internal information needed during reading and writing. Many formats require such temporary data, for things like temporary scanlines for compression and decompression, size and scanline counters, and subtype fields. It is not required by mental ray, but useful for the custom format code. This data structure is normally allocated by the open and create functions, used by the read and write functions, and released by the close function. Here, it contains a flag ascii that determines what format is being read (P3 ascii or P6 binary), the number of bits per component, and a scanline buffer.
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <shader.h>
#include <geoshader.h>
#include <mirelay.h>
typedef struct {
miBoolean ascii; /* read ascii, not binary */
int bits; /* source bit length per component */
int c; /* last char read from file */
miBoolean ok; /* error during ascii reading? */
char *buffer; /* RGB scanline buffer */
} Ppm_data;
The test function should return miTRUE if the file is a valid PPM file. It receives the first 256 bytes of the file, thus our function can easily check if this contains a PPM header:
/*
* Return miTRUE if <buf> is a valid ppm header
* beginning with "P3" for ascii files and "P6" for binary files.
*/
miBoolean customimage_test_ppm(
miImg_file * const ifp, /* open file info */
char * const buf) /* first 256 bytes of file */
{
return(buf[0] == 'P' && (buf[1] == '3' || buf[1] == '6'));
}
This is a very simple test function; most will try to check header data for consistency and perhaps even whether the file length is correct. False positives prevent all following custom file formats from being recognized because mental ray checks all file formats in order, with custom formats last.
The following functions create and open files. The actual file
is created and opened by raylib, our functions only have to write or
read the header, respectively, and allocate the private data
block Ppm_data. This data block is attached to
ifp→data.p reserved for this purpose. Note, that
memory for such data should always be allocated using MEM functions;
static variables are a bad idea becaue it makes these functions
non-reentrant, and may lead to difficult and unpredictable bugs.
mental ray can and often does load multiple texture files concurrently.
A single custom format may handle more than one data type. For
example, the create function may be changed to handle both 8 and
16 bits per pixel. In this case, it would check
ifp→type to determine what kind of header to write
and how large the private scanline buffer needs to be, and the write
function described later would write the data in a different format.
This can happen only if the typemap argument of
mi_img_custom_format
(see below) has more than one bit set. This method is strongly
recommended, and avoids creating one custom format per data type.
/*
* Create a ppm P6 file.
* The header is ascii, the rest binary.
* The ifp->data.p field is used to attach custom data.
*/
miBoolean customimage_create_ppm(
miImg_file * const ifp) /* open file info */
{
Ppm_data *data;
fprintf((FILE *)ifp->fp.p, "P6\n# created by custom format demo\n");
fprintf((FILE *)ifp->fp.p, "%d %d 255\n", ifp->width, ifp->height);
data = (Ppm_data *)mi_mem_allocate(sizeof(Ppm_data));
data->ascii = miFALSE;
data->buffer = (char *)mi_mem_allocate(3 * ifp->width);
ifp->data.p = (void *)data;
return(miTRUE);
}
/*
* Open a ppm file for reading.
* Allocate all data structures and interpret the header.
* The header is always ascii, the rest is either ascii or binary.
*/
miBoolean customimage_open_ppm(
miImg_file * const ifp, /* open file info */
char * const buf) /* first 256 bytes of file */
{
Ppm_data *data; /* file information */
int c; /* current char from input file */
int field[3]; /* header numbers: width, height, 255*/
int nfield = 0; /* next number to read, 0..2, 3=done */
fgetc((FILE *)ifp->fp.p); /* skip magic no */
fgetc((FILE *)ifp->fp.p);
fgetc((FILE *)ifp->fp.p);
for (;;) {
if (c == '#')
do c = fgetc((FILE *)ifp->fp.p);
while (c != '\n' && c != EOF);
else if (c == '\n' && nfield == 3)
break;
else if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
c = fgetc((FILE *)ifp->fp.p);
else if (isdigit(c)) {
field[nfield] = 0;
do {
field[nfield] = field[nfield] * 10 + c - '0';
c = fgetc((FILE *)ifp->fp.p);
} while (isdigit(c));
nfield++;
} else {
mi_img_custom_format_error(ifp, miIMG_ERR_DECODE, 0);
return(miFALSE);
}
}
ifp->width = field[0];
ifp->height = field[1];
ifp->comp = 3;
ifp->bits = 8;
data = (Ppm_data *)mi_mem_allocate(sizeof(Ppm_data));
data->ascii = buf[1] == '3';
if (field[2] > 255) {
if (!data->ascii || field[2] > 65535) {
mi_img_custom_format_error(ifp, miIMG_ERR_SUBTYPE, 0);
return(miFALSE);
} else
data->bits = 16;
} else
data->bits = 8;
data->buffer = (char *)mi_mem_allocate(3 * ifp->width);
ifp->data.p = (void *)data;
return(miTRUE);
}
Closing the actual file on disk is done by raylib. This function exists only to release the allocated private data upon close. It is good practice to clear the pointer afterwards to prevent releasing the pointer more than once.
/*
* Close the ppm file.
* Release all data structures that have been allocated by
* the open and create routines. This routine is also called
* when an open or create failed, to clean up things that
* have already been successfully allocated.
*/
miBoolean customimage_close_ppm(
miImg_file * const ifp)
{
if (ifp->data.p) {
mi_mem_release((char *)((Ppm_data *)ifp->data.p)->buffer);
mi_mem_release((char *)ifp->data.p);
ifp->data.p = 0;
}
return(miTRUE);
}
The read function reads the next scanline into the line buffer maintained by raylib. Its operation depends on whether the open function has recognized a P3 or P6 header, and has set the private ascii flag accordingly. 16-bit ascii data is scaled back to 8 bits.
/*
* Read one integer from the file; used by the ascii format.
* Set the error flag if something unexpected is read.
*/
static int read_int(
miImg_file * const ifp) /* open file */
{
int n = 0; /* returned int */
miBoolean minus; /* shouldn't happen */
Ppm_data *data = (Ppm_data *)ifp->data.p;
int c = data->c;
while (c == ' ' || c == '\t' || c == '\n' || c == '\r')
c = fgetc((FILE *)ifp->fp.p);
if (minus = c == '-')
c = fgetc((FILE *)ifp->fp.p);
if (!isdigit(c)) {
mi_img_custom_format_error(ifp, miIMG_ERR_DECODE, 0);
data->ok = miFALSE;
}
while (isdigit(c)) {
n = n * 10 + c - '0';
c = fgetc((FILE *)ifp->fp.p);
}
if (data->bits > 8)
n >>= 8;
data->c = c;
return(minus ? 0 : n > 255 ? 255 : n);
}
/*
* Read one RGB scanline from the file, either in ascii or binary format.
*/
miBoolean customimage_read_ppm(
miImg_file * const ifp, /* open file */
miImg_line * const line) /* ptrs to component buffers */
{
Ppm_data *data = (Ppm_data *)ifp->data.p;
register char *p = data->buffer;
register miUchar *r = miIMG_LINEACCESS(line, miIMG_R);
register miUchar *g = miIMG_LINEACCESS(line, miIMG_G);
register miUchar *b = miIMG_LINEACCESS(line, miIMG_B);
register int x;
data->ok = miTRUE;
if (data->ascii) {
data->c = '\n';
for (x=ifp->width; x; x--) {
*r++ = read_int(ifp);
*g++ = read_int(ifp);
*b++ = read_int(ifp);
}
} else {
if (fread(p, ifp->width, 3, (FILE *)ifp->fp.p) != 3) {
mi_img_custom_format_error(ifp, miIMG_ERR_READ, errno);
return(miFALSE);
}
for (x=ifp->width; x; x--) {
*r++ = *p++;
*g++ = *p++;
*b++ = *p++;
}
}
return(data->ok);
}
Writing a scanline of pixel data is very similar, except that only one format (binary P6) is supported here.
/*
* Rrite one RGB scanline to the file.
*/
miBoolean customimage_write_ppm(
miImg_file * const ifp, /* open file */
miImg_line * const line) /* ptrs to component buffers */
{
Ppm_data *data = (Ppm_data *)ifp->data.p;
register unsigned char *q = (unsigned char *)data->buffer;
register miUchar *r = miIMG_LINEACCESS(line, miIMG_R);
register miUchar *g = miIMG_LINEACCESS(line, miIMG_G);
register miUchar *b = miIMG_LINEACCESS(line, miIMG_B);
register int x;
for (x=ifp->width; x; x--, q+=3) {
q[0] = *r++;
q[1] = *g++;
q[2] = *b++;
}
if (fwrite(data->buffer, ifp->width, 3, (FILE *)ifp->fp.p) != 3) {
mi_img_custom_format_error(ifp, miIMG_ERR_WRITE, errno);
return(miFALSE);
}
return(miTRUE);
}
Finally, all the custom functions need to be registered with raylib. After registering, the format is available and supported just like any builtin format. The mi_img_custom_format function can fail only if an illegal format or a null name is passed.
/*
* Install the functions in raylib.
* The name "module_init" was chosen so this file can be compiled
* to a library and linked into raylib. raylib will always call
* module_init in a library it has just loaded.
*/
DLLEXPORT void module_init(void)
{
if (!mi_img_custom_format(miIMG_FORMAT_CUSTOM_0,
customimage_test_ppm,
customimage_create_ppm,
customimage_open_ppm,
customimage_close_ppm,
customimage_read_ppm,
customimage_write_ppm,
miTRUE, /* top line first */
"foo", /* standard extension */
1 << miIMG_TYPE_RGB, /* allowed types */
miIMG_TYPE_RGB, /* preferred data type*/
0)) /* always 0 */
mi_error("internal error: failed to create foo image format");
}
Copyright © 1986, 2015
NVIDIA ARC GmbH. All rights reserved.