blob: 3b4d0de5549902c6c5337b15a79613aca1300e81 [file] [log] [blame]
// ==========================================================
// PNM (PPM, PGM, PBM) Loader and Writer
//
// Design and implementation by
// - Floris van den Berg (flvdberg@wxs.nl)
// - Hervé Drolon (drolon@infonie.fr)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================
#include "FreeImage.h"
#include "Utilities.h"
// ==========================================================
// Internal functions
// ==========================================================
/**
Get an integer value from the actual position pointed by handle
*/
static int
GetInt(FreeImageIO *io, fi_handle handle) {
char c = 0;
BOOL bFirstChar;
// skip forward to start of next number
if(!io->read_proc(&c, 1, 1, handle)) {
throw FI_MSG_ERROR_PARSING;
}
while (1) {
// eat comments
if (c == '#') {
// if we're at a comment, read to end of line
bFirstChar = TRUE;
while (1) {
if(!io->read_proc(&c, 1, 1, handle)) {
throw FI_MSG_ERROR_PARSING;
}
if (bFirstChar && c == ' ') {
// loop off 1 sp after #
bFirstChar = FALSE;
} else if (c == '\n') {
break;
}
}
}
if (c >= '0' && c <='9') {
// we've found what we were looking for
break;
}
if(!io->read_proc(&c, 1, 1, handle)) {
throw FI_MSG_ERROR_PARSING;
}
}
// we're at the start of a number, continue until we hit a non-number
int i = 0;
while (1) {
i = (i * 10) + (c - '0');
if(!io->read_proc(&c, 1, 1, handle)) {
throw FI_MSG_ERROR_PARSING;
}
if (c < '0' || c > '9') {
break;
}
}
return i;
}
/**
Read a WORD value taking into account the endianess issue
*/
static inline WORD
ReadWord(FreeImageIO *io, fi_handle handle) {
WORD level = 0;
io->read_proc(&level, 2, 1, handle);
#ifndef FREEIMAGE_BIGENDIAN
SwapShort(&level); // PNM uses the big endian convention
#endif
return level;
}
/**
Write a WORD value taking into account the endianess issue
*/
static inline void
WriteWord(FreeImageIO *io, fi_handle handle, const WORD value) {
WORD level = value;
#ifndef FREEIMAGE_BIGENDIAN
SwapShort(&level); // PNM uses the big endian convention
#endif
io->write_proc(&level, 2, 1, handle);
}
// ==========================================================
// Plugin Interface
// ==========================================================
static int s_format_id;
// ==========================================================
// Plugin Implementation
// ==========================================================
static const char * DLL_CALLCONV
Format() {
return "PNM";
}
static const char * DLL_CALLCONV
Description() {
return "Portable Network Media";
}
static const char * DLL_CALLCONV
Extension() {
return "pbm,pgm,ppm";
}
static const char * DLL_CALLCONV
RegExpr() {
return NULL;
}
static const char * DLL_CALLCONV
MimeType() {
return "image/freeimage-pnm";
}
static BOOL DLL_CALLCONV
Validate(FreeImageIO *io, fi_handle handle) {
BYTE pbm_id1[] = { 0x50, 0x31 };
BYTE pbm_id2[] = { 0x50, 0x34 };
BYTE pgm_id1[] = { 0x50, 0x32 };
BYTE pgm_id2[] = { 0x50, 0x35 };
BYTE ppm_id1[] = { 0x50, 0x33 };
BYTE ppm_id2[] = { 0x50, 0x36 };
BYTE signature[2] = { 0, 0 };
io->read_proc(signature, 1, sizeof(pbm_id1), handle);
if (memcmp(pbm_id1, signature, sizeof(pbm_id1)) == 0)
return TRUE;
if (memcmp(pbm_id2, signature, sizeof(pbm_id2)) == 0)
return TRUE;
if (memcmp(pgm_id1, signature, sizeof(pgm_id1)) == 0)
return TRUE;
if (memcmp(pgm_id2, signature, sizeof(pgm_id2)) == 0)
return TRUE;
if (memcmp(ppm_id1, signature, sizeof(ppm_id1)) == 0)
return TRUE;
if (memcmp(ppm_id2, signature, sizeof(ppm_id2)) == 0)
return TRUE;
return FALSE;
}
static BOOL DLL_CALLCONV
SupportsExportDepth(int depth) {
return (
(depth == 1) ||
(depth == 8) ||
(depth == 24)
);
}
static BOOL DLL_CALLCONV
SupportsExportType(FREE_IMAGE_TYPE type) {
return (
(type == FIT_BITMAP) ||
(type == FIT_UINT16) ||
(type == FIT_RGB16)
);
}
static BOOL DLL_CALLCONV
SupportsNoPixels() {
return TRUE;
}
// ----------------------------------------------------------
static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
char id_one = 0, id_two = 0;
int x, y;
FIBITMAP *dib = NULL;
RGBQUAD *pal; // pointer to dib palette
int i;
if (!handle) {
return NULL;
}
BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
try {
FREE_IMAGE_TYPE image_type = FIT_BITMAP; // standard image: 1-, 8-, 24-bit
// Read the first two bytes of the file to determine the file format
// "P1" = ascii bitmap, "P2" = ascii greymap, "P3" = ascii pixmap,
// "P4" = raw bitmap, "P5" = raw greymap, "P6" = raw pixmap
io->read_proc(&id_one, 1, 1, handle);
io->read_proc(&id_two, 1, 1, handle);
if ((id_one != 'P') || (id_two < '1') || (id_two > '6')) {
// signature error
throw FI_MSG_ERROR_MAGIC_NUMBER;
}
// Read the header information: width, height and the 'max' value if any
int width = GetInt(io, handle);
int height = GetInt(io, handle);
int maxval = 1;
if((id_two == '2') || (id_two == '5') || (id_two == '3') || (id_two == '6')) {
maxval = GetInt(io, handle);
if((maxval <= 0) || (maxval > 65535)) {
FreeImage_OutputMessageProc(s_format_id, "Invalid max value : %d", maxval);
throw (const char*)NULL;
}
}
// Create a new DIB
switch (id_two) {
case '1':
case '4':
// 1-bit
dib = FreeImage_AllocateHeader(header_only, width, height, 1);
break;
case '2':
case '5':
if(maxval > 255) {
// 16-bit greyscale
image_type = FIT_UINT16;
dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height);
} else {
// 8-bit greyscale
dib = FreeImage_AllocateHeader(header_only, width, height, 8);
}
break;
case '3':
case '6':
if(maxval > 255) {
// 48-bit RGB
image_type = FIT_RGB16;
dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height);
} else {
// 24-bit RGB
dib = FreeImage_AllocateHeader(header_only, width, height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
}
break;
}
if (dib == NULL) {
throw FI_MSG_ERROR_DIB_MEMORY;
}
// Build a greyscale palette if needed
if(image_type == FIT_BITMAP) {
switch(id_two) {
case '1':
case '4':
pal = FreeImage_GetPalette(dib);
pal[0].rgbRed = pal[0].rgbGreen = pal[0].rgbBlue = 0;
pal[1].rgbRed = pal[1].rgbGreen = pal[1].rgbBlue = 255;
break;
case '2':
case '5':
pal = FreeImage_GetPalette(dib);
for (i = 0; i < 256; i++) {
pal[i].rgbRed =
pal[i].rgbGreen =
pal[i].rgbBlue = (BYTE)i;
}
break;
default:
break;
}
}
if(header_only) {
// header only mode
return dib;
}
// Read the image...
switch(id_two) {
case '1':
case '4':
// write the bitmap data
if (id_two == '1') { // ASCII bitmap
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
if (GetInt(io, handle) == 0)
bits[x >> 3] |= (0x80 >> (x & 0x7));
else
bits[x >> 3] &= (0xFF7F >> (x & 0x7));
}
}
} else { // Raw bitmap
int line = CalculateLine(width, 1);
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < line; x++) {
io->read_proc(&bits[x], 1, 1, handle);
bits[x] = ~bits[x];
}
}
}
return dib;
case '2':
case '5':
if(image_type == FIT_BITMAP) {
// write the bitmap data
if(id_two == '2') { // ASCII greymap
int level = 0;
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = GetInt(io, handle);
bits[x] = (BYTE)((255 * level) / maxval);
}
}
} else { // Raw greymap
BYTE level = 0;
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
io->read_proc(&level, 1, 1, handle);
bits[x] = (BYTE)((255 * (int)level) / maxval);
}
}
}
}
else if(image_type == FIT_UINT16) {
// write the bitmap data
if(id_two == '2') { // ASCII greymap
int level = 0;
for (y = 0; y < height; y++) {
WORD *bits = (WORD*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = GetInt(io, handle);
bits[x] = (WORD)((65535 * (double)level) / maxval);
}
}
} else { // Raw greymap
WORD level = 0;
for (y = 0; y < height; y++) {
WORD *bits = (WORD*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = ReadWord(io, handle);
bits[x] = (WORD)((65535 * (double)level) / maxval);
}
}
}
}
return dib;
case '3':
case '6':
if(image_type == FIT_BITMAP) {
// write the bitmap data
if (id_two == '3') { // ASCII pixmap
int level = 0;
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = GetInt(io, handle);
bits[FI_RGBA_RED] = (BYTE)((255 * level) / maxval); // R
level = GetInt(io, handle);
bits[FI_RGBA_GREEN] = (BYTE)((255 * level) / maxval); // G
level = GetInt(io, handle);
bits[FI_RGBA_BLUE] = (BYTE)((255 * level) / maxval); // B
bits += 3;
}
}
} else { // Raw pixmap
BYTE level = 0;
for (y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
io->read_proc(&level, 1, 1, handle);
bits[FI_RGBA_RED] = (BYTE)((255 * (int)level) / maxval); // R
io->read_proc(&level, 1, 1, handle);
bits[FI_RGBA_GREEN] = (BYTE)((255 * (int)level) / maxval); // G
io->read_proc(&level, 1, 1, handle);
bits[FI_RGBA_BLUE] = (BYTE)((255 * (int)level) / maxval); // B
bits += 3;
}
}
}
}
else if(image_type == FIT_RGB16) {
// write the bitmap data
if (id_two == '3') { // ASCII pixmap
int level = 0;
for (y = 0; y < height; y++) {
FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = GetInt(io, handle);
bits[x].red = (WORD)((65535 * (double)level) / maxval); // R
level = GetInt(io, handle);
bits[x].green = (WORD)((65535 * (double)level) / maxval); // G
level = GetInt(io, handle);
bits[x].blue = (WORD)((65535 * (double)level) / maxval); // B
}
}
} else { // Raw pixmap
WORD level = 0;
for (y = 0; y < height; y++) {
FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
level = ReadWord(io, handle);
bits[x].red = (WORD)((65535 * (double)level) / maxval); // R
level = ReadWord(io, handle);
bits[x].green = (WORD)((65535 * (double)level) / maxval); // G
level = ReadWord(io, handle);
bits[x].blue = (WORD)((65535 * (double)level) / maxval); // B
}
}
}
}
return dib;
}
} catch (const char *text) {
if(dib) FreeImage_Unload(dib);
if(NULL != text) {
switch(id_two) {
case '1':
case '4':
FreeImage_OutputMessageProc(s_format_id, text);
break;
case '2':
case '5':
FreeImage_OutputMessageProc(s_format_id, text);
break;
case '3':
case '6':
FreeImage_OutputMessageProc(s_format_id, text);
break;
}
}
}
return NULL;
}
static BOOL DLL_CALLCONV
Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
// ----------------------------------------------------------
// PNM Saving
// ----------------------------------------------------------
//
// Output format :
//
// Bit depth flags file format
// ------------- -------------- -----------
// 1-bit / pixel PNM_SAVE_ASCII PBM (P1)
// 1-bit / pixel PNM_SAVE_RAW PBM (P4)
// 8-bit / pixel PNM_SAVE_ASCII PGM (P2)
// 8-bit / pixel PNM_SAVE_RAW PGM (P5)
// 24-bit / pixel PNM_SAVE_ASCII PPM (P3)
// 24-bit / pixel PNM_SAVE_RAW PPM (P6)
// ----------------------------------------------------------
int x, y;
char buffer[256]; // temporary buffer whose size should be enough for what we need
if(!dib || !handle) return FALSE;
FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib);
int bpp = FreeImage_GetBPP(dib);
int width = FreeImage_GetWidth(dib);
int height = FreeImage_GetHeight(dib);
// Find the appropriate magic number for this file type
int magic = 0;
int maxval = 255;
switch(image_type) {
case FIT_BITMAP:
switch (bpp) {
case 1 :
magic = 1; // PBM file (B & W)
break;
case 8 :
magic = 2; // PGM file (Greyscale)
break;
case 24 :
magic = 3; // PPM file (RGB)
break;
default:
return FALSE; // Invalid bit depth
}
break;
case FIT_UINT16:
magic = 2; // PGM file (Greyscale)
maxval = 65535;
break;
case FIT_RGB16:
magic = 3; // PPM file (RGB)
maxval = 65535;
break;
default:
return FALSE;
}
if (flags == PNM_SAVE_RAW)
magic += 3;
// Write the header info
sprintf(buffer, "P%d\n%d %d\n", magic, width, height);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
if (bpp != 1) {
sprintf(buffer, "%d\n", maxval);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
}
// Write the image data
///////////////////////
if(image_type == FIT_BITMAP) {
switch(bpp) {
case 24 : // 24-bit RGB, 3 bytes per pixel
{
if (flags == PNM_SAVE_RAW) {
for (y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
io->write_proc(&bits[FI_RGBA_RED], 1, 1, handle); // R
io->write_proc(&bits[FI_RGBA_GREEN], 1, 1, handle); // G
io->write_proc(&bits[FI_RGBA_BLUE], 1, 1, handle); // B
bits += 3;
}
}
} else {
int length = 0;
for (y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
sprintf(buffer, "%3d %3d %3d ", bits[FI_RGBA_RED], bits[FI_RGBA_GREEN], bits[FI_RGBA_BLUE]);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length += 12;
if(length > 58) {
// No line should be longer than 70 characters
sprintf(buffer, "\n");
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length = 0;
}
bits += 3;
}
}
}
}
break;
case 8: // 8-bit greyscale
{
if (flags == PNM_SAVE_RAW) {
for (y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
io->write_proc(&bits[x], 1, 1, handle);
}
}
} else {
int length = 0;
for (y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
sprintf(buffer, "%3d ", bits[x]);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length += 4;
if (length > 66) {
// No line should be longer than 70 characters
sprintf(buffer, "\n");
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length = 0;
}
}
}
}
}
break;
case 1: // 1-bit B & W
{
int color;
if (flags == PNM_SAVE_RAW) {
for(y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for(x = 0; x < (int)FreeImage_GetLine(dib); x++)
io->write_proc(&bits[x], 1, 1, handle);
}
} else {
int length = 0;
for (y = 0; y < height; y++) {
// write the scanline to disc
BYTE *bits = FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < (int)FreeImage_GetLine(dib) * 8; x++) {
color = (bits[x>>3] & (0x80 >> (x & 0x07))) != 0;
sprintf(buffer, "%c ", color ? '1':'0');
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length += 2;
if (length > 68) {
// No line should be longer than 70 characters
sprintf(buffer, "\n");
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length = 0;
}
}
}
}
}
break;
}
} // if(FIT_BITMAP)
else if(image_type == FIT_UINT16) { // 16-bit greyscale
if (flags == PNM_SAVE_RAW) {
for (y = 0; y < height; y++) {
// write the scanline to disc
WORD *bits = (WORD*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
WriteWord(io, handle, bits[x]);
}
}
} else {
int length = 0;
for (y = 0; y < height; y++) {
// write the scanline to disc
WORD *bits = (WORD*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
sprintf(buffer, "%5d ", bits[x]);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length += 6;
if (length > 64) {
// No line should be longer than 70 characters
sprintf(buffer, "\n");
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length = 0;
}
}
}
}
}
else if(image_type == FIT_RGB16) { // 48-bit RGB
if (flags == PNM_SAVE_RAW) {
for (y = 0; y < height; y++) {
// write the scanline to disc
FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
WriteWord(io, handle, bits[x].red); // R
WriteWord(io, handle, bits[x].green); // G
WriteWord(io, handle, bits[x].blue); // B
}
}
} else {
int length = 0;
for (y = 0; y < height; y++) {
// write the scanline to disc
FIRGB16 *bits = (FIRGB16*)FreeImage_GetScanLine(dib, height - 1 - y);
for (x = 0; x < width; x++) {
sprintf(buffer, "%5d %5d %5d ", bits[x].red, bits[x].green, bits[x].blue);
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length += 18;
if(length > 52) {
// No line should be longer than 70 characters
sprintf(buffer, "\n");
io->write_proc(&buffer, (unsigned int)strlen(buffer), 1, handle);
length = 0;
}
}
}
}
}
return TRUE;
}
// ==========================================================
// Init
// ==========================================================
void DLL_CALLCONV
InitPNM(Plugin *plugin, int format_id) {
s_format_id = format_id;
plugin->format_proc = Format;
plugin->description_proc = Description;
plugin->extension_proc = Extension;
plugin->regexpr_proc = RegExpr;
plugin->open_proc = NULL;
plugin->close_proc = NULL;
plugin->pagecount_proc = NULL;
plugin->pagecapability_proc = NULL;
plugin->load_proc = Load;
plugin->save_proc = Save;
plugin->validate_proc = Validate;
plugin->mime_proc = MimeType;
plugin->supports_export_bpp_proc = SupportsExportDepth;
plugin->supports_export_type_proc = SupportsExportType;
plugin->supports_icc_profiles_proc = NULL;
plugin->supports_no_pixels_proc = SupportsNoPixels;
}