https://doi.org/10.5201/ipol.2012.g-tvdc
Tip revision: 13cac45f201f2e0e4a336450abb835be0ddd9359 authored by Software Heritage on 12 June 2012, 00:00:00 UTC
ipol: Deposit 1194 in collection ipol
ipol: Deposit 1194 in collection ipol
Tip revision: 13cac45
imageio.c
/**
* @file imageio.c
* @brief Implements ReadImage and WriteImage functions
* @author Pascal Getreuer <getreuer@gmail.com>
*
* Two high-level functions are provided, \c ReadImage and \c WriteImage, for
* reading and writing image BMP, JPEG, PNG, and TIFF files. The desired
* format of the image data can be specified to \c ReadImage for how to return
* the data (and similarly to \c WriteImage for how it should interpret the
* data). Formatting options allow specifying the datatype of the components,
* conversion to grayscale, channel ordering, interleaved vs. planar, and
* row-major vs. column-major.
*
* \c ReadImage automatically detects the format of the image being read so
* that the format does not need to be supplied explicitly. \c WriteImage
* infers the file format from the file extension.
*
* Also included is a function \c IdentifyImageType to guess the file type (BMP,
* JPEG, PNG, TIFF, and a few other formats) from the file header's magic
* numbers without reading the image.
*
* Support for BMP reading and writing is native: BMP reading supports 1-, 2-,
* 4-, 8-, 16-, 32-bit uncompressed, RLE, and bitfield images; BMP writing is
* limited to 24-bit uncompressed. The implementation calls libjpeg, libpng,
* and libtiff to handle JPEG, PNG, and TIFF images.
*
*
* Copyright (c) 2010-2012, Pascal Getreuer
* All rights reserved.
*
* This program is free software: you can use, modify and/or
* redistribute it under the terms of the simplified BSD License. You
* should have received a copy of this license along this program. If
* not, see <http://www.opensource.org/licenses/bsd-license.html>.
*/
#include <string.h>
#include <ctype.h>
#include "imageio.h"
#ifdef USE_LIBPNG
#include <zlib.h>
#include <png.h>
#if PNG_LIBPNG_VER < 10400
/* For compatibility with older libpng */
#define png_set_expand_gray_1_2_4_to_8 png_set_gray_1_2_4_to_8
#endif
#endif
#ifdef USE_LIBTIFF
#include <tiffio.h>
#endif
#ifdef USE_LIBJPEG
#include <jpeglib.h>
#include <setjmp.h>
#endif
/** @brief Buffer size to use for BMP file I/O */
#define FILE_BUFFER_CAPACITY (1024*4)
#define ROUNDCLAMPF(x) ((x < 0.0f) ? 0 : \
((x > 1.0f) ? 255 : (uint8_t)(255.0f*(x) + 0.5f)))
#define ROUNDCLAMP(x) ((x < 0.0) ? 0 : \
((x > 1.0) ? 255 : (uint8_t)(255.0*(x) + 0.5)))
/** @brief Case-insensitive test to see if String ends with Suffix */
static int StringEndsWith(const char *String, const char *Suffix)
{
unsigned i, StringLength = strlen(String), SuffixLength = strlen(Suffix);
if(StringLength < SuffixLength)
return 0;
String += StringLength - SuffixLength;
for(i = 0; i < SuffixLength; i++)
if(tolower(String[i]) != tolower(Suffix[i]))
return 0;
return 1;
}
/** @brief Fill an image with a color */
static void FillImage(uint32_t *Image, int Width, int Height, uint32_t Color)
{
int x, y;
if(Image)
for(y = 0; y < Height; y++, Image += Width)
for(x = 0; x < Width; x++)
Image[x] = Color;
}
/**
* @brief Check use of color and alpha, and count number of distinct colors
* @param NumColors set by the routine to the number of unique colors
* @param UseColor set to 1 if the image is not grayscale
* @param UseAlpha set to 1 if the image alpha is not constant 255
* @param Image pointer to U8 RGBA interleaved image data
* @param Width, Height dimensions of the image
* @return pointer to a color palette with NumColors entries or NULL if the
* number of distinct colors exceeds 256.
*
* This routine checks whether an RGBA image makes use of color and alpha, and
* constructs a palette if the number of distinct colors is 256 or fewer. This
* information is useful for writing image files with smaller file size.
*/
static uint32_t *GetImagePalette(int *NumColors, int *UseColor, int *UseAlpha,
const uint32_t *Image, int Width, int Height)
{
const int MaxColors = 256;
uint32_t *Palette = NULL;
uint32_t Pixel;
int x, y, i, Red, Green, Blue, Alpha;
if(!UseColor || !NumColors || !UseAlpha)
return NULL;
else if(!Image
|| !(Palette = (uint32_t *)Malloc(sizeof(uint32_t)*MaxColors)))
{
*NumColors = -1;
*UseColor = *UseAlpha = 1;
return NULL;
}
*NumColors = *UseColor = *UseAlpha = 0;
for(y = 0; y < Height; y++)
{
for(x = 0; x < Width; x++)
{
Pixel = *(Image++);
Red = ((uint8_t *)&Pixel)[0];
Green = ((uint8_t *)&Pixel)[1];
Blue = ((uint8_t *)&Pixel)[2];
Alpha = ((uint8_t *)&Pixel)[3];
if(Red != Green || Red != Blue) /* Check color */
*UseColor = 1;
if(Alpha != 255) /* Check alpha */
*UseAlpha = 1;
/* Check Palette colors (if *NumColors != -1) */
for(i = 0; i < *NumColors; i++)
if(Pixel == Palette[i])
break;
if(i == *NumColors)
{
if(i < MaxColors)
{ /* Add new color to Palette */
Palette[i] = Pixel;
(*NumColors)++;
}
else
{ /* Maximum size for Palette exceeded */
Free(Palette);
Palette = NULL;
*NumColors = -1; /* Don't check Palette colors */
}
}
}
}
return Palette;
}
/** @brief Read a 16-bit little Endian word from File */
static uint16_t ReadWordLE(FILE *File)
{
uint16_t w;
w = (uint16_t) getc(File);
w |= ((uint16_t) getc(File) << 8);
return w;
}
/** @brief Read a 32-bit little Endian double word from File */
static uint32_t ReadDWordLE(FILE *File)
{
uint32_t dw;
dw = (uint32_t) getc(File);
dw |= ((uint32_t) getc(File) << 8);
dw |= ((uint32_t) getc(File) << 16);
dw |= ((uint32_t) getc(File) << 24);
return dw;
}
/** @brief Write a 16-bit word in little Endian format */
static void WriteWordLE(uint16_t w, FILE *File)
{
putc(w & 0xFF, File);
putc((w & 0xFF00) >> 8, File);
}
/** @brief Write a 32-bit double word in little Endian format */
static void WriteDWordLE(uint32_t dw, FILE *File)
{
putc(dw & 0xFF, File);
putc((dw & 0xFF00) >> 8, File);
putc((dw & 0xFF0000) >> 16, File);
putc((dw & 0xFF000000) >> 24, File);
}
/** @brief Internal function for reading 1-bit BMP */
static int ReadBmp1Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette)
{
int RowPadding = (-(Width+7)/8)&3;
int x, y, Bit;
unsigned Code;
Image += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, Image -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width;)
{
Code = getc(File);
for(Bit = 7; Bit >= 0 && x < Width; Bit--, Code <<= 1)
Image[x++] = Palette[(Code & 0x80) ? 1:0];
}
for(x = RowPadding; x; x--)
getc(File); /* Skip padding bytes at the end of the row */
}
return 1;
}
/** @brief Internal function for reading 4-bit BMP */
static int ReadBmp4Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette)
{
int RowPadding = (-(Width+1)/2)&3;
int x, y;
unsigned Code;
Image += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, Image -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width;)
{
Code = getc(File);
Image[x++] = Palette[(Code & 0xF0) >> 4];
if(x < Width)
Image[x++] = Palette[Code & 0x0F];
}
for(x = RowPadding; x; x--)
getc(File); /* Skip padding bytes at the end of the row */
}
return 1;
}
/** @brief Internal function for reading 4-bit RLE-compressed BMP */
static int ReadBmp4BitRle(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette)
{
int x, y, dy, k;
unsigned Count, Value;
uint32_t ColorH, ColorL;
FillImage(Image, Width, Height, Palette[0]);
Image += ((long int)Width)*((long int)Height - 1);
for(x = 0, y = Height; y;)
{
if(feof(File))
return 0;
Count = getc(File);
Value = getc(File);
if(!Count)
{ /* Count = 0 is the escape code */
switch(Value)
{
case 0: /* End of line */
Image -= Width;
x = 0;
y--;
break;
case 1: /* End of bitmap */
return 1;
case 2: /* Delta */
x += getc(File);
dy = getc(File);
y -= dy;
Image -= dy*Width;
if(x >= Width || y < 0)
return 0;
break;
default: /* Read a run of uncompressed data (Value = length of run) */
Count = k = Value;
if(x >= Width)
return 0;
do
{
Value = getc(File);
Image[x++] = Palette[(Value & 0xF0) >> 4];
if(x >= Width)
break;
if(--k)
{
Image[x++] = Palette[Value & 0x0F];
k--;
if(x >= Width)
break;
}
}while(k);
if(((Count + 1)/2) & 1)
getc(File); /* Padding for word align */
}
}
else
{ /* Run of pixels (Count = length of run) */
ColorH = Palette[(Value & 0xF0) >> 4];
ColorL = Palette[Value & 0xF];
if(x >= Width)
return 0;
do
{
Image[x++] = ColorH;
Count--;
if(x >= Width)
break;
if(Count)
{
Image[x++] = ColorL;
Count--;
if(x >= Width)
break;
}
}while(Count);
}
}
return 1;
}
/** @brief Internal function for reading 8-bit BMP */
static int ReadBmp8Bit(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette)
{
int RowPadding = (-Width)&3;
int x, y;
Image += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, Image -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width; x++)
Image[x] = Palette[getc(File) & 0xFF];
for(x = RowPadding; x; x--)
getc(File); /* Skip padding bytes at the end of the row */
}
return 1;
}
/** @brief Internal function for reading 8-bit RLE-compressed BMP */
static int ReadBmp8BitRle(uint32_t *Image, int Width, int Height, FILE *File, const uint32_t *Palette)
{
int x, y, dy, k;
unsigned Count, Value;
uint32_t Color;
FillImage(Image, Width, Height, Palette[0]);
Image += ((long int)Width)*((long int)Height - 1);
for(x = 0, y = Height; y;)
{
if(feof(File))
return 0;
Count = getc(File);
Value = getc(File);
if(!Count)
{ /* Count = 0 is the escape code */
switch(Value)
{
case 0: /* End of line */
Image -= Width;
x = 0;
y--;
break;
case 1: /* End of bitmap */
return 1;
case 2: /* Delta */
x += getc(File);
dy = getc(File);
y -= dy;
Image -= dy*Width;
if(x >= Width || y < 0)
return 0;
break;
default: /* Read a run of uncompressed data (Value = length of run) */
Count = k = Value;
do
{
if(x >= Width)
break;
Image[x++] = Palette[getc(File) & 0xFF];
}while(--k);
if(Count&1)
getc(File); /* Padding for word align */
}
}
else
{ /* Run of pixels equal to Value (Count = length of run) */
Color = Palette[Value & 0xFF];
do
{
if(x >= Width)
break;
Image[x++] = Color;
}while(--Count);
}
}
return 1;
}
/** @brief Internal function for reading 24-bit BMP */
static int ReadBmp24Bit(uint32_t *Image, int Width, int Height, FILE *File)
{
uint8_t *ImagePtr = (uint8_t *)Image;
int RowPadding = (-3*Width)&3;
int x, y;
Width <<= 2;
ImagePtr += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, ImagePtr -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width; x += 4)
{
ImagePtr[x+3] = 255; /* Set alpha */
ImagePtr[x+2] = getc(File); /* Read blue component */
ImagePtr[x+1] = getc(File); /* Read green component */
ImagePtr[x+0] = getc(File); /* Read red component */
}
for(x = RowPadding; x; x--)
getc(File); /* Skip padding bytes at the end of the row */
}
return 1;
}
/** @brief Internal function for determining bit shifts in bitfield BMP */
static void GetMaskShifts(uint32_t Mask, int *LeftShift, int *RightShift)
{
int Shift = 0, BitCount = 0;
if(!Mask)
{
*LeftShift = 0;
*RightShift = 0;
return;
}
while(!(Mask & 1)) /* Find the first true bit */
{
Mask >>= 1;
++Shift;
}
/* Adjust the result for scaling to 8-bit quantities */
while(Mask & 1) /* Count the number of true bits */
{
Mask >>= 1;
++BitCount;
}
/* Compute a signed shift (right is positive) */
Shift += BitCount - 8;
if(Shift >= 0)
{
*LeftShift = 0;
*RightShift = Shift;
}
else
{
*LeftShift = -Shift;
*RightShift = 0;
}
}
/** @brief Internal function for reading 16-bit BMP */
static int ReadBmp16Bit(uint32_t *Image, int Width, int Height, FILE *File,
uint32_t RedMask, uint32_t GreenMask, uint32_t BlueMask, uint32_t AlphaMask)
{
uint8_t *ImagePtr = (uint8_t *)Image;
uint32_t Code;
int RowPadding = (-2*Width)&3;
int RedLeftShift, GreenLeftShift, BlueLeftShift, AlphaLeftShift;
int RedRightShift, GreenRightShift, BlueRightShift, AlphaRightShift;
int x, y;
GetMaskShifts(RedMask, &RedLeftShift, &RedRightShift);
GetMaskShifts(GreenMask, &GreenLeftShift, &GreenRightShift);
GetMaskShifts(BlueMask, &BlueLeftShift, &BlueRightShift);
GetMaskShifts(AlphaMask, &AlphaLeftShift, &AlphaRightShift);
Width <<= 2;
ImagePtr += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, ImagePtr -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width; x += 4)
{
Code = ReadWordLE(File);
/* By the Windows 4.x BMP specification, color component masks must be contiguous
[http://www.fileformat.info/format/bmp/egff.htm]. So we can decode the bitfields
by bitwise AND with the mask and applying a bitshift.*/
ImagePtr[x+3] = ((Code & AlphaMask) >> AlphaRightShift) << AlphaLeftShift;
ImagePtr[x+2] = ((Code & BlueMask ) >> BlueRightShift ) << BlueLeftShift;
ImagePtr[x+1] = ((Code & GreenMask) >> GreenRightShift) << GreenLeftShift;
ImagePtr[x+0] = ((Code & RedMask ) >> RedRightShift ) << RedLeftShift;
}
for(x = RowPadding; x; x--)
getc(File); /* Skip padding bytes at the end of the row */
}
return 1;
}
/** @brief Internal function for reading 32-bit BMP */
static int ReadBmp32Bit(uint32_t *Image, int Width, int Height, FILE *File,
uint32_t RedMask, uint32_t GreenMask, uint32_t BlueMask, uint32_t AlphaMask)
{
uint8_t *ImagePtr;
uint32_t Code;
int RedLeftShift, GreenLeftShift, BlueLeftShift, AlphaLeftShift;
int RedRightShift, GreenRightShift, BlueRightShift, AlphaRightShift;
int x, y;
GetMaskShifts(RedMask, &RedLeftShift, &RedRightShift);
GetMaskShifts(GreenMask, &GreenLeftShift, &GreenRightShift);
GetMaskShifts(BlueMask, &BlueLeftShift, &BlueRightShift);
GetMaskShifts(AlphaMask, &AlphaLeftShift, &AlphaRightShift);
Width <<= 2;
ImagePtr = (uint8_t *)Image + ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, ImagePtr -= Width)
{
if(feof(File))
return 0;
for(x = 0; x < Width; x += 4)
{
Code = ReadDWordLE(File);
/* By the Windows 4.x BMP specification, color component masks must be contiguous
[http://www.fileformat.info/format/bmp/egff.htm]. So we can decode the bitfields
by bitwise AND with the mask and applying a bitshift.*/
ImagePtr[x+3] = ((Code & AlphaMask) >> AlphaRightShift) << AlphaLeftShift;
ImagePtr[x+2] = ((Code & BlueMask ) >> BlueRightShift ) << BlueLeftShift;
ImagePtr[x+1] = ((Code & GreenMask) >> GreenRightShift) << GreenLeftShift;
ImagePtr[x+0] = ((Code & RedMask ) >> RedRightShift ) << RedLeftShift;
}
}
return 1;
}
/**
* @brief Read a BMP (Windows Bitmap) image file as RGBA data
*
* @param Image, Width, Height pointers to be filled with the pointer
* to the image data and the image dimensions.
* @param File stdio FILE pointer pointing to the beginning of the BMP file
*
* @return 1 on success, 0 on failure
*
* This function is called by \c ReadImage to read BMP images. Before calling
* \c ReadBmp, the caller should open \c File as a FILE pointer in binary read
* mode. When \c ReadBmp is complete, the caller should close \c File.
*/
static int ReadBmp(uint32_t **Image, int *Width, int *Height, FILE *File)
{
uint32_t *Palette = NULL;
uint8_t *PalettePtr;
long int ImageDataOffset, InfoSize;
unsigned i, NumPlanes, BitsPerPixel, Compression, NumColors;
uint32_t RedMask, GreenMask, BlueMask, AlphaMask;
int Success = 0, Os2Bmp;
uint8_t Magic[2];
*Image = NULL;
*Width = *Height = 0;
fseek(File, 0, SEEK_SET);
Magic[0] = getc(File);
Magic[1] = getc(File);
if(!(Magic[0] == 0x42 && Magic[1] == 0x4D) /* Verify the magic numbers */
|| fseek(File, 8, SEEK_CUR)) /* Skip the reserved fields */
{
ErrorMessage("Invalid BMP header.\n");
goto Catch;
}
ImageDataOffset = ReadDWordLE(File);
InfoSize = ReadDWordLE(File);
/* Read the info header */
if(InfoSize < 12)
{
ErrorMessage("Invalid BMP info header.\n");
goto Catch;
}
if((Os2Bmp = (InfoSize == 12))) /* This is an OS/2 V1 infoheader */
{
*Width = (int)ReadWordLE(File);
*Height = (int)ReadWordLE(File);
NumPlanes = (unsigned)ReadWordLE(File);
BitsPerPixel = (unsigned)ReadWordLE(File);
Compression = 0;
NumColors = 0;
RedMask = 0x00FF0000;
GreenMask = 0x0000FF00;
BlueMask = 0x000000FF;
AlphaMask = 0xFF000000;
}
else
{
*Width = abs((int)ReadDWordLE(File));
*Height = abs((int)ReadDWordLE(File));
NumPlanes = (unsigned)ReadWordLE(File);
BitsPerPixel = (unsigned)ReadWordLE(File);
Compression = (unsigned)ReadDWordLE(File);
fseek(File, 12, SEEK_CUR);
NumColors = (unsigned)ReadDWordLE(File);
fseek(File, 4, SEEK_CUR);
RedMask = ReadDWordLE(File);
GreenMask = ReadDWordLE(File);
BlueMask = ReadDWordLE(File);
AlphaMask = ReadDWordLE(File);
}
/* Check for problems or unsupported compression modes */
if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE)
{
ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n");
goto Catch;
}
if(feof(File) || NumPlanes != 1 || Compression > 3)
goto Catch;
/* Allocate the image data */
if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)*((long int)*Width)*((long int)*Height))))
goto Catch;
/* Read palette */
if(BitsPerPixel <= 8)
{
fseek(File, 14 + InfoSize, SEEK_SET);
if(!NumColors)
NumColors = 1 << BitsPerPixel;
if(!(Palette = (uint32_t *)Malloc(sizeof(uint32_t)*256)))
goto Catch;
for(i = 0, PalettePtr = (uint8_t *)Palette; i < NumColors; i++)
{
PalettePtr[3] = 255; /* Set alpha */
PalettePtr[2] = getc(File); /* Read blue component */
PalettePtr[1] = getc(File); /* Read green component */
PalettePtr[0] = getc(File); /* Read red component */
PalettePtr += 4;
if(!Os2Bmp)
getc(File); /* Skip extra byte (for non-OS/2 bitmaps) */
}
for(; i < 256; i++) /* Fill the rest of the palette with the first color */
Palette[i] = Palette[0];
}
if(fseek(File, ImageDataOffset, SEEK_SET) || feof(File))
{
ErrorMessage("File error.\n");
goto Catch;
}
/*** Read the bitmap image data ***/
switch(Compression)
{
case 0: /* Uncompressed data */
switch(BitsPerPixel)
{
case 1: /* Read 1-bit uncompressed indexed data */
Success = ReadBmp1Bit(*Image, *Width, *Height, File, Palette);
break;
case 4: /* Read 4-bit uncompressed indexed data */
Success = ReadBmp4Bit(*Image, *Width, *Height, File, Palette);
break;
case 8: /* Read 8-bit uncompressed indexed data */
Success = ReadBmp8Bit(*Image, *Width, *Height, File, Palette);
break;
case 24: /* Read 24-bit BGR image data */
Success = ReadBmp24Bit(*Image, *Width, *Height, File);
break;
case 16: /* Read 16-bit data */
Success = ReadBmp16Bit(*Image, *Width, *Height, File,
0x001F << 10, 0x001F << 5, 0x0001F, 0);
break;
case 32: /* Read 32-bit BGRA image data */
Success = ReadBmp32Bit(*Image, *Width, *Height, File,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
break;
}
break;
case 1: /* 8-bit RLE */
if(BitsPerPixel == 8)
Success = ReadBmp8BitRle(*Image, *Width, *Height, File, Palette);
break;
case 2: /* 4-bit RLE */
if(BitsPerPixel == 4)
Success = ReadBmp4BitRle(*Image, *Width, *Height, File, Palette);
break;
case 3: /* Bitfields data */
switch(BitsPerPixel)
{
case 16: /* Read 16-bit bitfields data */
Success = ReadBmp16Bit(*Image, *Width, *Height, File,
RedMask, GreenMask, BlueMask, AlphaMask);
break;
case 32: /* Read 32-bit bitfields data */
Success = ReadBmp32Bit(*Image, *Width, *Height, File,
RedMask, GreenMask, BlueMask, AlphaMask);
break;
}
break;
}
if(!Success)
ErrorMessage("Error reading BMP data.\n");
Catch: /* There was a problem, clean up and exit */
if(Palette)
Free(Palette);
if(!Success && *Image)
Free(*Image);
return Success;
}
/**
* @brief Write a BMP image
*
* @param Image pointer to RGBA image data
* @param Width, Height the image dimensions
* @param File stdio FILE pointer
*
* @return 1 on success, 0 on failure
*
* This function is called by \c WriteImage to write BMP images. The caller
* should open \c File in binary write mode. When \c WriteBmp is complete,
* the caller should close \c File.
*
* The image is generally saved in uncompressed 24-bit RGB format. But where
* possible, the image is saved using an 8-bit palette for a substantial
* decrease in file size. The image data is always saved losslessly.
*
* @note The alpha channel is lost when saving to BMP. It is possible to write
* the alpha channel in a 32-bit BMP image, however, such images are not
* widely supported. RGB 24-bit BMP on the other hand is well supported.
*/
static int WriteBmp(const uint32_t *Image, int Width, int Height, FILE *File)
{
const uint8_t *ImagePtr = (uint8_t *)Image;
uint32_t *Palette = NULL;
uint32_t Pixel;
long int ImageSize;
int UsePalette, NumColors, UseColor, UseAlpha;
int x, y, i, RowPadding, Success = 0;
if(!Image)
return 0;
Palette = GetImagePalette(&NumColors, &UseColor, &UseAlpha,
Image, Width, Height);
/* Decide whether to use 8-bit palette or 24-bit RGB format */
if(Palette && 2*NumColors < Width*Height)
UsePalette = 1;
else
UsePalette = NumColors = 0;
/* Tell File to use buffering */
setvbuf(File, 0, _IOFBF, FILE_BUFFER_CAPACITY);
if(UsePalette)
{
RowPadding = (-Width)&3;
ImageSize = (Width + RowPadding)*((long int)Height);
}
else
{
RowPadding = (-3*Width)&3;
ImageSize = (3*Width + RowPadding)*((long int)Height);
}
/*** Write the header ***/
/* Write the BMP header */
putc(0x42, File); /* Magic numbers */
putc(0x4D, File);
/* Filesize */
WriteDWordLE(54 + 4*NumColors + ImageSize, File);
WriteDWordLE(0, File); /* Reserved fields */
WriteDWordLE(54 + 4*NumColors, File); /* Image data offset */
/* Write the infoheader */
WriteDWordLE(40, File); /* Infoheader size */
WriteDWordLE(Width, File); /* Image width */
WriteDWordLE(Height, File); /* Image height */
WriteWordLE(1, File); /* Number of colorplanes */
WriteWordLE((UsePalette) ? 8:24, File); /* Bits per pixel */
WriteDWordLE(0, File); /* Compression method (none) */
WriteDWordLE(ImageSize, File); /* Image size */
WriteDWordLE(2835, File); /* HResolution (2835=72dpi) */
WriteDWordLE(2835, File); /* VResolution */
/* Number of colors */
WriteDWordLE((!UsePalette || NumColors == 256) ? 0:NumColors, File);
WriteDWordLE(0, File); /* Important colors */
if(ferror(File))
{
ErrorMessage("Error during write to file.\n");
goto Catch;
}
if(UsePalette)
{ /* Write the Palette */
for(i = 0; i < NumColors; i++)
{
Pixel = Palette[i];
putc(((uint8_t *)&Pixel)[2], File); /* Blue */
putc(((uint8_t *)&Pixel)[1], File); /* Green */
putc(((uint8_t *)&Pixel)[0], File); /* Red */
putc(0, File); /* Unused */
}
}
/* Write the image data */
Width <<= 2;
ImagePtr += ((long int)Width)*((long int)Height - 1);
for(y = Height; y; y--, ImagePtr -= Width)
{
if(UsePalette)
{ /* 8-bit palette image data */
for(x = 0; x < Width; x += 4)
{
Pixel = *((uint32_t *)(ImagePtr + x));
for(i = 0; i < NumColors; i++)
if(Pixel == Palette[i])
break;
putc(i, File);
}
}
else
{ /* 24-bit RGB image data */
for(x = 0; x < Width; x += 4)
{
putc(ImagePtr[x+2], File); /* Write blue component */
putc(ImagePtr[x+1], File); /* Write green component */
putc(ImagePtr[x+0], File); /* Write red component */
}
}
for(x = RowPadding; x; x--) /* Write row padding */
putc(0, File);
}
if(ferror(File))
{
ErrorMessage("Error during write to file.\n");
goto Catch;
}
Success = 1;
Catch:
if(Palette)
Free(Palette);
return Success;
}
#ifdef USE_LIBJPEG
/**
* @brief Struct that assists in customizing libjpeg error management
*
* This struct is used in combination with JerrExit (static function defined
* here in utiljpeg.c) to have control over how libjpeg errors are displayed.
*/
typedef struct{
struct jpeg_error_mgr pub;
jmp_buf jmpbuf;
} hooked_jerr;
/** @brief Callback for displaying libjpeg errors */
METHODDEF(void) JerrExit(j_common_ptr cinfo)
{
hooked_jerr *Jerr = (hooked_jerr *) cinfo->err;
(*cinfo->err->output_message)(cinfo);
longjmp(Jerr->jmpbuf, 1);
}
/**
* @brief Read a JPEG (Joint Picture Experts Group) image file as RGBA data
*
* @param Image, Width, Height pointers to be filled with the pointer
* to the image data and the image dimensions.
* @param File stdio FILE pointer pointing to the beginning of the BMP file
*
* @return 1 on success, 0 on failure
*
* This function is called by \c ReadImage to read JPEG images. Before calling
* \c ReadJpeg, the caller should open \c File as a FILE pointer in binary read
* mode. When \c ReadJpeg is complete, the caller should close \c File.
*/
static int ReadJpeg(uint32_t **Image, int *Width, int *Height, FILE *File)
{
struct jpeg_decompress_struct cinfo;
hooked_jerr Jerr;
JSAMPARRAY Buffer;
uint8_t *ImagePtr;
unsigned i, RowSize;
*Image = 0;
*Width = *Height = 0;
cinfo.err = jpeg_std_error(&Jerr.pub);
Jerr.pub.error_exit = JerrExit;
if(setjmp(Jerr.jmpbuf))
goto Catch; /* If this code is reached, libjpeg has signaled an error. */
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, File);
jpeg_read_header(&cinfo, 1);
cinfo.out_color_space = JCS_RGB; /* Ask for RGB image data */
jpeg_start_decompress(&cinfo);
*Width = (int)cinfo.output_width;
*Height = (int)cinfo.output_height;
if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE)
{
ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n");
jpeg_abort_decompress(&cinfo);
goto Catch;
}
/* Allocate image memory */
if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)
*((size_t)*Width)*((size_t)*Height))))
{
jpeg_abort_decompress(&cinfo);
goto Catch;
}
/* Allocate a one-row-high array that will go away when done */
RowSize = cinfo.output_width * cinfo.output_components;
Buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo,
JPOOL_IMAGE, RowSize, 1);
ImagePtr = (uint8_t *)*Image;
while(cinfo.output_scanline < cinfo.output_height)
for(jpeg_read_scanlines(&cinfo, Buffer, 1), i = 0; i < RowSize; i += 3)
{
*(ImagePtr++) = Buffer[0][i]; /* Red */
*(ImagePtr++) = Buffer[0][i+1]; /* Green */
*(ImagePtr++) = Buffer[0][i+2]; /* Blue */
*(ImagePtr++) = 0xFF;
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return 1;
Catch:
if(*Image)
Free(*Image);
*Width = *Height = 0;
jpeg_destroy_decompress(&cinfo);
return 0;
}
/**
* @brief Write a JPEG image as RGB data
*
* @param Image pointer to RGBA image data
* @param Width, Height the image dimensions
* @param File stdio FILE pointer
*
* @return 1 on success, 0 on failure
*
* This function is called by \c WriteImage to write JPEG images. The caller
* should open \c File in binary write mode. When \c WriteJpeg is complete,
* the caller should close \c File.
*
* @note The alpha channel is lost when saving to JPEG since the JPEG format
* does not support RGBA images. (It is in principle possible to store
* four channels in a JPEG as a CMYK image, but storing alpha this way
* is strange.)
*/
static int WriteJpeg(const uint32_t *Image, int Width, int Height,
FILE *File, int Quality)
{
struct jpeg_compress_struct cinfo;
hooked_jerr Jerr;
uint8_t *Buffer = 0, *ImagePtr;
unsigned i, RowSize;
if(!Image)
return 0;
cinfo.err = jpeg_std_error(&Jerr.pub);
Jerr.pub.error_exit = JerrExit;
if(setjmp(Jerr.jmpbuf))
goto Catch; /* If this code is reached, libjpeg has signaled an error. */
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, File);
cinfo.image_width = Width;
cinfo.image_height = Height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, (Quality < 100) ? Quality : 100, 1);
jpeg_start_compress(&cinfo, 1);
RowSize = 3*Width;
ImagePtr = (uint8_t *)Image;
if(!(Buffer = (uint8_t *)Malloc(RowSize)))
goto Catch;
while(cinfo.next_scanline < cinfo.image_height)
{
for(i = 0; i < RowSize; i += 3)
{
Buffer[i] = ImagePtr[0]; /* Red */
Buffer[i+1] = ImagePtr[1]; /* Green */
Buffer[i+2] = ImagePtr[2]; /* Blue */
ImagePtr += 4;
}
jpeg_write_scanlines(&cinfo, &Buffer, 1);
}
if(Buffer)
Free(Buffer);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return 1;
Catch:
if(Buffer)
Free(Buffer);
jpeg_destroy_compress(&cinfo);
return 0;
}
#endif /* USE_LIBJPEG */
#ifdef USE_LIBPNG
/**
* @brief Read a PNG (Portable Network Graphics) image file as RGBA data
*
* @param Image, Width, Height pointers to be filled with the pointer
* to the image data and the image dimensions.
* @param File stdio FILE pointer pointing to the beginning of the PNG file
*
* @return 1 on success, 0 on failure
*
* This function is called by \c ReadImage to read PNG images. Before calling
* \c ReadPng, the caller should open \c File as a FILE pointer in binary read
* mode. When \c ReadPng is complete, the caller should close \c File.
*/
static int ReadPng(uint32_t **Image, int *Width, int *Height, FILE *File)
{
png_bytep *RowPointers;
png_byte Header[8];
png_structp Png;
png_infop Info;
png_uint_32 PngWidth, PngHeight;
int BitDepth, ColorType, InterlaceType;
unsigned Row;
*Image = 0;
*Width = *Height = 0;
/* Check that file is a PNG file */
if(fread(Header, 1, 8, File) != 8 || png_sig_cmp(Header, 0, 8))
return 0;
/* Read the info header */
if(!(Png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))
|| !(Info = png_create_info_struct(Png)))
{
if(Png)
png_destroy_read_struct(&Png, (png_infopp)NULL, (png_infopp)NULL);
return 0;
}
if(setjmp(png_jmpbuf(Png)))
goto Catch; /* If this code is reached, libpng has signaled an error. */
png_init_io(Png, File);
png_set_sig_bytes(Png, 8);
png_set_user_limits(Png, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE);
png_read_info(Png, Info);
png_get_IHDR(Png, Info, &PngWidth, &PngHeight, &BitDepth, &ColorType,
&InterlaceType, (int*)NULL, (int*)NULL);
*Width = (int)PngWidth;
*Height = (int)PngHeight;
/* Tell libpng to convert everything to 32-bit RGBA */
if(ColorType == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(Png);
if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
png_set_expand_gray_1_2_4_to_8(Png);
if(ColorType == PNG_COLOR_TYPE_GRAY || ColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(Png);
if(png_get_valid(Png, Info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(Png);
png_set_strip_16(Png);
png_set_filler(Png, 0xFF, PNG_FILLER_AFTER);
png_set_interlace_handling(Png);
png_read_update_info(Png, Info);
/* Allocate image memory and row pointers */
if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)
*((size_t)*Width)*((size_t)*Height)))
|| !(RowPointers = (png_bytep *)Malloc(sizeof(png_bytep)
*PngHeight)))
goto Catch;
for(Row = 0; Row < PngHeight; Row++)
RowPointers[Row] = (png_bytep)(*Image + PngWidth*Row);
/* Read the image data */
png_read_image(Png, RowPointers);
Free(RowPointers);
png_destroy_read_struct(&Png, &Info, (png_infopp)NULL);
return 1;
Catch:
if(*Image)
Free(*Image);
*Width = *Height = 0;
png_destroy_read_struct(&Png, &Info, (png_infopp)NULL);
return 0;
}
/**
* @brief Write a PNG image
*
* @param Image pointer to RGBA image data
* @param Width, Height the image dimensions
* @param File stdio FILE pointer
*
* @return 1 on success, 0 on failure
*
* This function is called by \c WriteImage to write PNG images. The caller
* should open \c File in binary write mode. When \c WritePng is complete,
* the caller should close \c File.
*
* The image is written as 8-bit grayscale, indexed (PLTE), indexed with
* transparent colors (PLTE+tRNS), RGB, or RGBA data (in that order of
* preference) depending on the image data to encourage smaller file size. The
* image data is always saved losslessly. In principle, PNG can also make use
* of the pixel bit depth (1, 2, 4, 8, or 16) to reduce the file size further,
* but it is not done here.
*/
static int WritePng(const uint32_t *Image, int Width, int Height, FILE *File)
{
const uint32_t *ImagePtr;
uint32_t *Palette = NULL;
uint8_t *RowBuffer;
png_structp Png;
png_infop Info;
png_color PngPalette[256];
png_byte PngTrans[256];
uint32_t Pixel;
int PngColorType, NumColors, UseColor, UseAlpha;
int x, y, i, Success = 0;
if(!Image)
return 0;
if(!(RowBuffer = (uint8_t *)Malloc(4*Width)))
return 0;
if(!(Png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL))
|| !(Info = png_create_info_struct(Png)))
{
if(Png)
png_destroy_write_struct(&Png, (png_infopp)NULL);
Free(RowBuffer);
return 0;
}
if(setjmp(png_jmpbuf(Png)))
{ /* If this code is reached, libpng has signaled an error. */
goto Catch;
}
/* Configure PNG output */
png_init_io(Png, File);
png_set_compression_level(Png, Z_BEST_COMPRESSION);
Palette = GetImagePalette(&NumColors, &UseColor, &UseAlpha,
Image, Width, Height);
/* The PNG image is written according to the analysis of GetImagePalette */
if(Palette && UseColor)
PngColorType = PNG_COLOR_TYPE_PALETTE;
else if(UseAlpha)
PngColorType = PNG_COLOR_TYPE_RGB_ALPHA;
else if(UseColor)
PngColorType = PNG_COLOR_TYPE_RGB;
else
PngColorType = PNG_COLOR_TYPE_GRAY;
png_set_IHDR(Png, Info, Width, Height, 8, PngColorType,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if(PngColorType == PNG_COLOR_TYPE_PALETTE)
{
for(i = 0; i < NumColors; i++)
{
Pixel = Palette[i];
PngPalette[i].red = ((uint8_t *)&Pixel)[0];
PngPalette[i].green = ((uint8_t *)&Pixel)[1];
PngPalette[i].blue = ((uint8_t *)&Pixel)[2];
PngTrans[i] = ((uint8_t *)&Pixel)[3];
}
png_set_PLTE(Png, Info, PngPalette, NumColors);
if(UseAlpha)
png_set_tRNS(Png, Info, PngTrans, NumColors, NULL);
}
png_write_info(Png, Info);
for(y = 0, ImagePtr = Image; y < Height; y++, ImagePtr += Width)
{
switch(PngColorType)
{
case PNG_COLOR_TYPE_RGB_ALPHA:
png_write_row(Png, (png_bytep)ImagePtr);
break;
case PNG_COLOR_TYPE_RGB:
for(x = 0; x < Width; x++)
{
Pixel = ImagePtr[x];
RowBuffer[3*x + 0] = ((uint8_t *)&Pixel)[0];
RowBuffer[3*x + 1] = ((uint8_t *)&Pixel)[1];
RowBuffer[3*x + 2] = ((uint8_t *)&Pixel)[2];
}
png_write_row(Png, (png_bytep)RowBuffer);
break;
case PNG_COLOR_TYPE_GRAY:
for(x = 0; x < Width; x++)
{
Pixel = ImagePtr[x];
RowBuffer[x] = ((uint8_t *)&Pixel)[0];
}
png_write_row(Png, (png_bytep)RowBuffer);
break;
case PNG_COLOR_TYPE_PALETTE:
for(x = 0; x < Width; x++)
{
Pixel = ImagePtr[x];
for(i = 0; i < NumColors; i++)
if(Pixel == Palette[i])
break;
RowBuffer[x] = i;
}
png_write_row(Png, (png_bytep)RowBuffer);
break;
}
}
png_write_end(Png, Info);
Success = 1;
Catch:
if(Palette)
Free(Palette);
png_destroy_write_struct(&Png, &Info);
Free(RowBuffer);
return Success;
}
#endif /* USE_LIBPNG */
#ifdef USE_LIBTIFF
/**
* @brief Read a TIFF (Tagged Information File Format) image file as RGBA data
*
* @param Image, Width, Height pointers to be filled with the pointer
* to the image data and the image dimensions.
* @param File stdio FILE pointer pointing to the beginning of the PNG file
*
* @return 1 on success, 0 on failure
*
* This function is called by \c ReadImage to read TIFF images. Before calling
* \c ReadTiff, the caller should open \c File as a FILE pointer in binary read
* mode. When \c ReadTiff is complete, the caller should close \c File.
*/
static int ReadTiff(uint32_t **Image, int *Width, int *Height,
const char *FileName, unsigned Directory)
{
TIFF *Tiff;
uint32 ImageWidth, ImageHeight;
*Image = 0;
*Width = *Height = 0;
if(!(Tiff = TIFFOpen(FileName, "r")))
{
ErrorMessage("TIFFOpen failed to open file.\n");
return 0;
}
TIFFSetDirectory(Tiff, Directory);
TIFFGetField(Tiff, TIFFTAG_IMAGEWIDTH, &ImageWidth);
TIFFGetField(Tiff, TIFFTAG_IMAGELENGTH, &ImageHeight);
*Width = (int)ImageWidth;
*Height = (int)ImageHeight;
if(*Width > MAX_IMAGE_SIZE || *Height > MAX_IMAGE_SIZE)
{
ErrorMessage("Image dimensions exceed MAX_IMAGE_SIZE.\n");
goto Catch;
}
if(!(*Image = (uint32_t *)Malloc(sizeof(uint32_t)*ImageWidth*ImageHeight)))
goto Catch;
if(!TIFFReadRGBAImageOriented(Tiff, ImageWidth, ImageHeight,
(uint32 *)*Image, ORIENTATION_TOPLEFT, 1))
goto Catch;
TIFFClose(Tiff);
return 1;
Catch:
if(*Image)
Free(*Image);
*Width = *Height = 0;
TIFFClose(Tiff);
return 0;
}
/**
* @brief Write a TIFF image as RGBA data
*
* @param Image pointer to RGBA image data
* @param Width, Height the image dimensions
* @param File stdio FILE pointer
*
* @return 1 on success, 0 on failure
*
* This function is called by \c WriteImage to write TIFF images. The caller
* should open \c File in binary write mode. When \c WriteTiff is complete,
* the caller should close \c File.
*/
static int WriteTiff(const uint32_t *Image, int Width, int Height,
const char *FileName)
{
TIFF *Tiff;
uint16 Alpha = EXTRASAMPLE_ASSOCALPHA;
if(!Image)
return 0;
if(!(Tiff = TIFFOpen(FileName, "w")))
{
ErrorMessage("TIFFOpen failed to open file.\n");
return 0;
}
if(TIFFSetField(Tiff, TIFFTAG_IMAGEWIDTH, Width) != 1
|| TIFFSetField(Tiff, TIFFTAG_IMAGELENGTH, Height) != 1
|| TIFFSetField(Tiff, TIFFTAG_SAMPLESPERPIXEL, 4) != 1
|| TIFFSetField(Tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) != 1
|| TIFFSetField(Tiff, TIFFTAG_EXTRASAMPLES, 1, &Alpha) != 1
|| TIFFSetField(Tiff, TIFFTAG_BITSPERSAMPLE, 8) != 1
|| TIFFSetField(Tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT) != 1
|| TIFFSetField(Tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) != 1
/* Compression can be COMPRESSION_NONE, COMPRESSION_DEFLATE,
COMPRESSION_LZW, or COMPRESSION_JPEG */
|| TIFFSetField(Tiff, TIFFTAG_COMPRESSION, COMPRESSION_LZW) != 1)
{
ErrorMessage("TIFFSetField failed.\n");
TIFFClose(Tiff);
return 0;
}
if(TIFFWriteEncodedStrip(Tiff, 0, (tdata_t)Image,
4*((size_t)Width)*((size_t)Height)) < 0)
{
ErrorMessage("Error writing data to file.\n");
TIFFClose(Tiff);
return 0;
}
TIFFClose(Tiff);
return 1;
}
#endif /* USE_LIBTIFF */
/** @brief Convert from RGBA U8 to a specified format */
static void *ConvertToFormat(uint32_t *Src, int Width, int Height,
unsigned Format)
{
const int NumPixels = Width*Height;
const int NumChannels = (Format & IMAGEIO_GRAYSCALE) ?
1 : ((Format & IMAGEIO_STRIP_ALPHA) ? 3 : 4);
const int ChannelStride = (Format & IMAGEIO_PLANAR) ? NumPixels : 1;
const int ChannelStride2 = 2*ChannelStride;
const int ChannelStride3 = 3*ChannelStride;
double *DestD;
float *DestF;
uint8_t *DestU8;
uint32_t Pixel;
int Order[4] = {0, 1, 2, 3};
int i, x, y, PixelStride, RowStride;
PixelStride = (Format & IMAGEIO_PLANAR) ? 1 : NumChannels;
if(Format & IMAGEIO_COLUMNMAJOR)
{
RowStride = PixelStride;
PixelStride *= Height;
}
else
RowStride = Width*PixelStride;
if(Format & IMAGEIO_BGRFLIP)
{
Order[0] = 2;
Order[2] = 0;
}
if((Format & IMAGEIO_AFLIP) && !(Format & IMAGEIO_STRIP_ALPHA))
{
Order[3] = Order[2];
Order[2] = Order[1];
Order[1] = Order[0];
Order[0] = 3;
}
switch(Format & (IMAGEIO_U8 | IMAGEIO_SINGLE | IMAGEIO_DOUBLE))
{
case IMAGEIO_U8: /* Destination type is uint8_t */
if(!(DestU8 = (uint8_t *)Malloc(sizeof(uint8_t)*NumChannels*NumPixels)))
return NULL;
switch(NumChannels)
{
case 1: /* Convert RGBA U8 to grayscale U8 */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestU8[i] = (uint8_t)(0.299f*((uint8_t *)&Pixel)[0]
+ 0.587f*((uint8_t *)&Pixel)[1]
+ 0.114f*((uint8_t *)&Pixel)[2] + 0.5f);
}
break;
case 3: /* Convert RGBA U8 to RGB (or BGR) U8 */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestU8[i] = ((uint8_t *)&Pixel)[Order[0]];
DestU8[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]];
DestU8[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]];
}
break;
case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) U8 */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestU8[i] = ((uint8_t *)&Pixel)[Order[0]];
DestU8[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]];
DestU8[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]];
DestU8[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]];
}
break;
}
return DestU8;
case IMAGEIO_SINGLE: /* Destination type is float */
if(!(DestF = (float *)Malloc(sizeof(float)*NumChannels*NumPixels)))
return NULL;
switch(NumChannels)
{
case 1: /* Convert RGBA U8 to grayscale float */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestF[i] = 1.172549019607843070675535e-3f*((uint8_t *)&Pixel)[0]
+ 2.301960784313725357840079e-3f*((uint8_t *)&Pixel)[1]
+ 4.470588235294117808150007e-4f*((uint8_t *)&Pixel)[2];
}
break;
case 3: /* Convert RGBA U8 to RGB (or BGR) float */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestF[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0f;
DestF[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0f;
DestF[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0f;
}
break;
case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) float */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestF[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0f;
DestF[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0f;
DestF[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0f;
DestF[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]]/255.0f;
}
break;
}
return DestF;
case IMAGEIO_DOUBLE: /* Destination type is double */
if(!(DestD = (double *)Malloc(sizeof(double)*NumChannels*NumPixels)))
return NULL;
switch(NumChannels)
{
case 1: /* Convert RGBA U8 to grayscale double */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestD[i] = 1.172549019607843070675535e-3*((uint8_t *)&Pixel)[0]
+ 2.301960784313725357840079e-3*((uint8_t *)&Pixel)[1]
+ 4.470588235294117808150007e-4*((uint8_t *)&Pixel)[2];
}
break;
case 3: /* Convert RGBA U8 to RGB (or BGR) double */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestD[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0;
DestD[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0;
DestD[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0;
}
break;
case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) double */
for(y = 0; y < Height; y++, Src += Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
Pixel = Src[x];
DestD[i] = ((uint8_t *)&Pixel)[Order[0]]/255.0;
DestD[i + ChannelStride] = ((uint8_t *)&Pixel)[Order[1]]/255.0;
DestD[i + ChannelStride2] = ((uint8_t *)&Pixel)[Order[2]]/255.0;
DestD[i + ChannelStride3] = ((uint8_t *)&Pixel)[Order[3]]/255.0;
}
break;
}
return DestD;
default:
return NULL;
}
}
/** @brief Convert from a specified format to RGBA U8 */
static uint32_t *ConvertFromFormat(void *Src, int Width, int Height,
unsigned Format)
{
const int NumPixels = Width*Height;
const int NumChannels = (Format & IMAGEIO_GRAYSCALE) ?
1 : ((Format & IMAGEIO_STRIP_ALPHA) ? 3 : 4);
const int ChannelStride = (Format & IMAGEIO_PLANAR) ? NumPixels : 1;
const int ChannelStride2 = 2*ChannelStride;
const int ChannelStride3 = 3*ChannelStride;
double *SrcD = (double *)Src;
float *SrcF = (float *)Src;
uint8_t *SrcU8 = (uint8_t *)Src;
uint8_t *Dest, *DestPtr;
int Order[4] = {0, 1, 2, 3};
int i, x, y, PixelStride, RowStride;
if(!(Dest = (uint8_t *)Malloc(sizeof(uint32_t)*NumPixels)))
return NULL;
DestPtr = Dest;
PixelStride = (Format & IMAGEIO_PLANAR) ? 1 : NumChannels;
if(Format & IMAGEIO_COLUMNMAJOR)
{
RowStride = PixelStride;
PixelStride *= Height;
}
else
RowStride = Width*PixelStride;
if(Format & IMAGEIO_BGRFLIP)
{
Order[0] = 2;
Order[2] = 0;
}
if((Format & IMAGEIO_AFLIP) && !(Format & IMAGEIO_STRIP_ALPHA))
{
Order[3] = Order[2];
Order[2] = Order[1];
Order[1] = Order[0];
Order[0] = 3;
}
switch(Format & (IMAGEIO_U8 | IMAGEIO_SINGLE | IMAGEIO_DOUBLE))
{
case IMAGEIO_U8: /* Source type is uint8_t */
switch(NumChannels)
{
case 1: /* Convert grayscale U8 to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x] =
DestPtr[4*x + 1] =
DestPtr[4*x + 2] = SrcU8[i];
DestPtr[4*x + 3] = 255;
}
break;
case 3: /* Convert RGB (or BGR) U8 to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = SrcU8[i];
DestPtr[4*x + Order[1]] = SrcU8[i + ChannelStride];
DestPtr[4*x + Order[2]] = SrcU8[i + ChannelStride2];
DestPtr[4*x + 3] = 255;
}
break;
case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = SrcU8[i];
DestPtr[4*x + Order[1]] = SrcU8[i + ChannelStride];
DestPtr[4*x + Order[2]] = SrcU8[i + ChannelStride2];
DestPtr[4*x + Order[3]] = SrcU8[i + ChannelStride3];
}
break;
}
break;
case IMAGEIO_SINGLE: /* Source type is float */
switch(NumChannels)
{
case 1: /* Convert grayscale float to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x] =
DestPtr[4*x + 1] =
DestPtr[4*x + 2] = ROUNDCLAMPF(SrcF[i]);
DestPtr[4*x + 3] = 255;
}
break;
case 3: /* Convert RGBA U8 to RGB (or BGR) float */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = ROUNDCLAMPF(SrcF[i]);
DestPtr[4*x + Order[1]] = ROUNDCLAMPF(SrcF[i + ChannelStride]);
DestPtr[4*x + Order[2]] = ROUNDCLAMPF(SrcF[i + ChannelStride2]);
DestPtr[4*x + 3] = 255;
}
break;
case 4: /* Convert RGBA U8 to RGBA (or BGRA, ARGB, or ABGR) float */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = ROUNDCLAMPF(SrcF[i]);
DestPtr[4*x + Order[1]] = ROUNDCLAMPF(SrcF[i + ChannelStride]);
DestPtr[4*x + Order[2]] = ROUNDCLAMPF(SrcF[i + ChannelStride2]);
DestPtr[4*x + Order[3]] = ROUNDCLAMPF(SrcF[i + ChannelStride3]);
}
break;
}
break;
case IMAGEIO_DOUBLE: /* Source type is double */
switch(NumChannels)
{
case 1: /* Convert grayscale double to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x] =
DestPtr[4*x + 1] =
DestPtr[4*x + 2] = ROUNDCLAMP(SrcD[i]);
DestPtr[4*x + 3] = 255;
}
break;
case 3: /* Convert RGB (or BGR) double to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = ROUNDCLAMP(SrcD[i]);
DestPtr[4*x + Order[1]] = ROUNDCLAMP(SrcD[i + ChannelStride]);
DestPtr[4*x + Order[2]] = ROUNDCLAMP(SrcD[i + ChannelStride2]);
DestPtr[4*x + 3] = 255;;
}
break;
case 4: /* Convert RGBA (or BGRA, ARGB, or ABGR) double to RGBA U8 */
for(y = 0; y < Height; y++, DestPtr += 4*Width)
for(x = 0, i = RowStride*y; x < Width; x++, i += PixelStride)
{
DestPtr[4*x + Order[0]] = ROUNDCLAMP(SrcD[i]);
DestPtr[4*x + Order[1]] = ROUNDCLAMP(SrcD[i + ChannelStride]);
DestPtr[4*x + Order[2]] = ROUNDCLAMP(SrcD[i + ChannelStride2]);
DestPtr[4*x + Order[3]] = ROUNDCLAMP(SrcD[i + ChannelStride3]);
}
break;
}
break;
default:
return NULL;
}
return (uint32_t *)Dest;
}
/**
* @brief Identify the file type of an image file by its magic numbers
* @param Type destination buffer with space for at least 5 chars
* @param FileName image file name
* @return 1 on successful identification, 0 on failure.
*
* The routine fills Type with an identifying string. If there is an error
* or the file type is unknown, Type is set to a null string.
*/
int IdentifyImageType(char *Type, const char *FileName)
{
FILE *File;
uint32_t Magic;
Type[0] = '\0';
if(!(File = fopen(FileName, "rb")))
return 0;
/* Determine the file format by reading the first 4 bytes */
Magic = ((uint32_t)getc(File));
Magic |= ((uint32_t)getc(File)) << 8;
Magic |= ((uint32_t)getc(File)) << 16;
Magic |= ((uint32_t)getc(File)) << 24;
/* Test for errors */
if(ferror(File))
{
fclose(File);
return 0;
}
fclose(File);
if((Magic & 0x0000FFFFL) == 0x00004D42L) /* BMP */
strcpy(Type, "BMP");
else if((Magic & 0x00FFFFFFL) == 0x00FFD8FFL) /* JPEG/JFIF */
strcpy(Type, "JPEG");
else if(Magic == 0x474E5089L) /* PNG */
strcpy(Type, "PNG");
else if(Magic == 0x002A4949L || Magic == 0x2A004D4DL) /* TIFF */
strcpy(Type, "TIFF");
else if(Magic == 0x38464947L) /* GIF */
strcpy(Type, "GIF");
else if(Magic == 0x474E4D8AL) /* MNG */
strcpy(Type, "MNG");
else if((Magic & 0xF0FF00FFL) == 0x0001000AL /* PCX */
&& ((Magic >> 8) & 0xFF) < 6)
strcpy(Type, "PCX");
else
return 0;
return 1;
}
/**
* @brief Read an image file as 32-bit RGBA data
*
* @param Width, Height pointers to be filled with the image dimensions
* @param FileName image file name
* @param Format specifies the desired format for the image
*
* @return Pointer to the image data, or null on failure
*
* The calling syntax is that the filename is the input and \c Width,
* and \c Height and the returned pointer are outputs. \c ReadImage allocates
* memory for the image as one contiguous block of memory and returns a
* pointer. It is the responsibility of the caller to call \c Free on this
* pointer when done to release this memory.
*
* A non-null pointer indicates success. On failure, the returned pointer
* is null, and \c Width and \c Height are set to 0.
*
* The Format argument is used by specifying one of the data type options
*
* - IMAGEIO_U8: unsigned 8-bit components
* - IMAGEIO_SINGLE: float components
* - IMAGEIO_DOUBLE: double components
*
* and one of the channel options
*
* - IMAGEIO_GRAYSCALE: grayscale data
* - IMAGEIO_RGB: RGB color data (red is the first channel)
* - IMAGEIO_BGR: BGR color data (blue is the first channel)
* - IMAGEIO_RGBA: RGBA color+alpha data
* - IMAGEIO_BGRA: BGRA color+alpha data
* - IMAGEIO_ARGB: ARGB color+alpha data
* - IMAGEIO_ABGR: ABGR color+alpha data
*
* and optionally either or both of the ordering options
*
* - IMAGEIO_PLANAR: planar order instead of interleaved components
* - IMAGEIO_COLUMNMAJOR: column major order instead of row major order
*
@code
uint32_t *Image;
int Width, Height;
if(!(Image = (uint32_t *)ReadImage(&Width, &Height, "myimage.bmp",
IMAGEIO_U8 | IMAGEIO_RGBA)))
return 0;
printf("Read image of size %dx%d\n", Width, Height);
...
Free(Image);
@endcode
*
* With the default formatting IMAGEIO_U8 | IMAGEIO_RGBA, the image is
* organized in standard row major top-down 32-bit RGBA order. The image
* is organized as
@verbatim
(Top left) (Top right)
Image[0] Image[1] ... Image[Width-1]
Image[Width] Image[Width+1] ... Image[2*Width]
... ... ... ...
Image[Width*(Height-1)] ... ... Image[Width*Height-1]
(Bottom left) (Bottom right)
@endverbatim
* Each element \c Image[k] represents one RGBA pixel, which is a 32-bit
* bitfield. The components of pixel \c Image[k] can be unpacked as
@code
uint8_t *Component = (uint8_t *)&Image[k];
uint8_t Red = Component[0];
uint8_t Green = Component[1];
uint8_t Blue = Component[2];
uint8_t Alpha = Component[3];
@endcode
* Each component is an unsigned 8-bit integer value with range 0-255. Most
* images do not have alpha information, in which case the alpha component
* is set to value 255 (full opacity).
*
* With IMAGEIO_SINGLE or IMAGEIO_DOUBLE, the components are values in the
* range 0 to 1.
*/
void *ReadImage(int *Width, int *Height,
const char *FileName, unsigned Format)
{
void *Image = NULL;
uint32_t *ImageU8 = NULL;
FILE *File;
char Type[8];
IdentifyImageType(Type, FileName);
if(!(File = fopen(FileName, "rb")))
{
ErrorMessage("Unable to open file \"%s\".\n", FileName);
return 0;
}
if(!strcmp(Type, "BMP"))
{
if(!ReadBmp(&ImageU8, Width, Height, File))
ErrorMessage("Failed to read \"%s\".\n", FileName);
}
else if(!strcmp(Type, "JPEG"))
{
#ifdef USE_LIBJPEG
if(!(ReadJpeg(&ImageU8, Width, Height, File)))
ErrorMessage("Failed to read \"%s\".\n", FileName);
#else
ErrorMessage("File \"%s\" is a JPEG image.\n"
"Compile with USE_LIBJPEG to enable JPEG reading.\n",
FileName);
#endif
}
else if(!strcmp(Type, "PNG"))
{
#ifdef USE_LIBPNG
if(!(ReadPng(&ImageU8, Width, Height, File)))
ErrorMessage("Failed to read \"%s\".\n", FileName);
#else
ErrorMessage("File \"%s\" is a PNG image.\n"
"Compile with USE_LIBPNG to enable PNG reading.\n",
FileName);
#endif
}
else if(!strcmp(Type, "TIFF"))
{
#ifdef USE_LIBTIFF
fclose(File);
if(!(ReadTiff(&ImageU8, Width, Height, FileName, 0)))
ErrorMessage("Failed to read \"%s\".\n", FileName);
File = NULL;
#else
ErrorMessage("File \"%s\" is a TIFF image.\n"
"Compile with USE_LIBTIFF to enable TIFF reading.\n",
FileName);
#endif
}
else
{
/* File format is unsupported. */
if(Type[0])
ErrorMessage("File \"%s\" is a %s image.", FileName, Type);
else
ErrorMessage("File \"%s\" is an unrecognized format.", FileName);
fprintf(stderr, "\nSorry, only " READIMAGE_FORMATS_SUPPORTED " reading is supported.\n");
}
if(File)
fclose(File);
if(ImageU8 && Format)
{
Image = ConvertToFormat(ImageU8, *Width, *Height, Format);
Free(ImageU8);
}
else
Image = ImageU8;
return Image;
}
/**
* @brief Write an image file from 8-bit RGBA image data
*
* @param Image pointer to the image data
* @param Width, Height image dimensions
* @param FileName image file name
* @param Format specifies how the data is formatted (see ReadImage)
* @param Quality the JPEG image quality (between 0 and 100)
*
* @return 1 on success, 0 on failure
*
* The input \c Image should be a 32-bit RGBA image stored as in the
* description of \c ReadImage. \c WriteImage writes to \c FileName in the
* file format specified by its extension. If saving a JPEG image, the
* \c Quality argument specifies the quality factor (between 0 and 100).
* \c Quality has no effect on other formats.
*
* The return value indicates success with 1 or failure with 0.
*/
int WriteImage(void *Image, int Width, int Height,
const char *FileName, unsigned Format, int Quality)
{
FILE *File;
uint32_t *ImageU8;
enum {BMP_FORMAT, JPEG_FORMAT, PNG_FORMAT, TIFF_FORMAT} FileFormat;
int Success = 0;
if(!Image || Width <= 0 || Height <= 0)
{
ErrorMessage("Null image.\n");
ErrorMessage("Failed to write \"%s\".\n", FileName);
return 0;
}
if(StringEndsWith(FileName, ".bmp"))
FileFormat = BMP_FORMAT;
else if(StringEndsWith(FileName, ".jpg")
|| StringEndsWith(FileName, ".jpeg"))
{
FileFormat = JPEG_FORMAT;
#ifndef USE_LIBJPEG
ErrorMessage("Failed to write \"%s\".\n", FileName);
ErrorMessage("Compile with USE_LIBJPEG to enable JPEG writing.\n");
return 0;
#endif
}
else if(StringEndsWith(FileName, ".png"))
{
FileFormat = PNG_FORMAT;
#ifndef USE_LIBPNG
ErrorMessage("Failed to write \"%s\".\n", FileName);
ErrorMessage("Compile with USE_LIBPNG to enable PNG writing.\n");
return 0;
#endif
}
else if(StringEndsWith(FileName, ".tif")
|| StringEndsWith(FileName, ".tiff"))
{
FileFormat = TIFF_FORMAT;
#ifndef USE_LIBTIFF
ErrorMessage("Failed to write \"%s\".\n", FileName);
ErrorMessage("Compile with USE_LIBTIFF to enable TIFF writing.\n");
return 0;
#endif
}
else
{
ErrorMessage("Failed to write \"%s\".\n", FileName);
if(StringEndsWith(FileName, ".gif"))
ErrorMessage("GIF is not supported. ");
else if(StringEndsWith(FileName, ".mng"))
ErrorMessage("MNG is not supported. ");
else if(StringEndsWith(FileName, ".pcx"))
ErrorMessage("PCX is not supported. ");
else
ErrorMessage("Unable to determine format from extension.\n");
ErrorMessage("Sorry, only " WRITEIMAGE_FORMATS_SUPPORTED " writing is supported.\n");
return 0;
}
if(!(File = fopen(FileName, "wb")))
{
ErrorMessage("Unable to write to file \"%s\".\n", FileName);
return 0;
}
if(!(ImageU8 = ConvertFromFormat(Image, Width, Height, Format)))
return 0;
switch(FileFormat)
{
case BMP_FORMAT:
Success = WriteBmp(ImageU8, Width, Height, File);
break;
case JPEG_FORMAT:
#ifdef USE_LIBJPEG
Success = WriteJpeg(ImageU8, Width, Height, File, Quality);
#else
/* Dummy operation to avoid unused variable warning if compiled without
libjpeg. Note that execution returns above if Format == JPEG_FORMAT
and USE_LIBJPEG is undefined. */
Success = Quality;
#endif
break;
case PNG_FORMAT:
#ifdef USE_LIBPNG
Success = WritePng(ImageU8, Width, Height, File);
#endif
break;
case TIFF_FORMAT:
#ifdef USE_LIBTIFF
fclose(File);
Success = WriteTiff(ImageU8, Width, Height, FileName);
File = 0;
#endif
break;
}
if(!Success)
ErrorMessage("Failed to write \"%s\".\n", FileName);
Free(ImageU8);
if(File)
fclose(File);
return Success;
}