Files
bmplib/bmp-read.c
2025-06-22 02:05:35 +02:00

1774 lines
45 KiB
C

/* bmplib - bmp-read.c
*
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
* If not, see <https://www.gnu.org/licenses/>
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
#define BMPLIB_LIB
#include "config.h"
#include "bmplib.h"
#include "logging.h"
#include "bmp-common.h"
#include "bmp-read-icons.h"
#include "bmp-read.h"
const char* s_compression_name(int compression);
/*****************************************************************************
* bmpread_new
*****************************************************************************/
API BMPHANDLE bmpread_new(FILE *file)
{
BMPREAD rp = NULL;
if (!(rp = malloc(sizeof *rp))) {
goto abort;
}
memset(rp, 0, sizeof *rp);
rp->c.magic = HMAGIC_READ;
rp->undefined_mode = BMP_UNDEFINED_TO_ALPHA;
rp->orientation = BMP_ORIENT_BOTTOMUP;
rp->conv64 = BMP_CONV64_SRGB;
rp->result_format = BMP_FORMAT_INT;
rp->read_state = RS_INIT;
if (!(rp->c.log = logcreate()))
goto abort;
if (!file)
goto abort;
rp->file = file;
if (!(rp->fh = malloc(sizeof *rp->fh)))
goto abort;
memset(rp->fh, 0, sizeof *rp->fh);
if (!(rp->ih = malloc(sizeof *rp->ih)))
goto abort;
memset(rp->ih, 0, sizeof *rp->ih);
rp->insanity_limit = INSANITY_LIMIT << 20;
return (BMPHANDLE)(void*)rp;
abort:
if (rp)
br_free(rp);
return NULL;
}
/*****************************************************************************
* bmpread_load_info
*****************************************************************************/
static bool s_read_file_header(BMPREAD_R rp);
static bool s_read_info_header(BMPREAD_R rp);
static bool s_is_bmptype_supported(BMPREAD_R rp);
static struct Palette* s_read_palette(BMPREAD_R rp);
static bool s_read_colormasks(BMPREAD_R rp);
API BMPRESULT bmpread_load_info(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state >= RS_HEADER_OK)
return rp->getinfo_return;
if (!s_read_file_header(rp))
goto abort;
long pos;
unsigned type = rp->fh->type;
switch (rp->fh->type) {
case BMPFILE_BM:
/* ok */
break;
case BMPFILE_CI:
case BMPFILE_CP:
case BMPFILE_IC:
case BMPFILE_PT:
if (rp->read_state != RS_EXPECT_ICON_MASK) {
pos = icon_load_masks(rp);
if (pos < 0)
goto abort;
/* re-read the file header, because for color icons/pointers
* we started with the monochrome headers, and icon_load_masks()
* returned the pos of the actual color headers.
*/
rp->bytes_read = 0;
if (fseek(rp->file, pos, SEEK_SET)) {
logsyserr(rp->c.log, "Setting file position");
goto abort;
}
if (!s_read_file_header(rp))
goto abort;
if (rp->fh->type != type) {
logerr(rp->c.log, "Filetype mismatch: have 0x%04x, expected 0x%04x",
(unsigned)rp->fh->type, type);
goto abort;
}
rp->is_icon = true;
if (type == BMPFILE_CI || type == BMPFILE_CP)
rp->icon_is_mono = false;
else
rp->icon_is_mono = true;
rp->undefined_mode = BMP_UNDEFINED_LEAVE;
}
/* otherwise, state is RS_EXPECT_ICON_MASK, which means that
* icon_load_masks() is reading the AND/XOR masks (under a
* separate BMPHANDLE). We don't have to do anything special,
* just treat it as a normal BMP image, not as an icon/pointer
*/
break;
case BMPFILE_BA:
if (rp->is_arrayimg) {
logerr(rp->c.log, "Invalid nested bitmap array");
goto abort;
}
if (!icon_read_array(rp)) {
logerr(rp->c.log, "Failed to read icon array index");
goto abort;
}
rp->read_state = RS_ARRAY;
rp->getinfo_return = BMP_RESULT_ARRAY;
return rp->getinfo_return;
default:
logerr(rp->c.log, "Unkown BMP type 0x%04x\n", (unsigned int) rp->fh->type);
rp->lasterr = BMP_ERR_UNSUPPORTED;
goto abort;
}
#if ( LONG_MAX <= 0x7fffffffL )
if (rp->fh->offbits > (unsigned long)LONG_MAX) {
logerr(rp->c.log, "Invalid offset to image data: %lu", (unsigned long)rp->fh->offbits);
goto abort;
}
#endif
if (!s_read_info_header(rp))
goto abort;
rp->width = (int) rp->ih->width;
/* negative height flips the image vertically */
if (rp->ih->height < 0) {
if (rp->is_icon) {
logerr(rp->c.log, "Top-down orientation incompatible with icons/pointers");
rp->lasterr = BMP_ERR_HEADER;
goto abort;
}
if (rp->ih->height == INT_MIN) {
logerr(rp->c.log, "Unsupported image height %ld\n", (long) rp->ih->height);
rp->lasterr = BMP_ERR_UNSUPPORTED;
goto abort;
}
rp->orientation = BMP_ORIENT_TOPDOWN;
rp->height = -rp->ih->height;
} else {
rp->height = rp->ih->height;
}
if (rp->is_icon && rp->icon_is_mono)
rp->height /= 2;
if (rp->ih->compression == BI_RLE4 ||
rp->ih->compression == BI_RLE8 ||
rp->ih->compression == BI_OS2_RLE24) {
rp->rle = true;
}
if (rp->ih->compression == BI_JPEG || rp->ih->compression == BI_PNG) {
if (!cm_gobble_up(rp, rp->fh->offbits - rp->bytes_read)) {
logerr(rp->c.log, "while seeking to start of jpeg/png data");
goto abort;
}
if (rp->ih->compression == BI_JPEG) {
rp->jpeg = true;
rp->getinfo_return = BMP_RESULT_JPEG;
logerr(rp->c.log, "embedded JPEG data");
rp->lasterr = BMP_ERR_JPEG;
return BMP_RESULT_JPEG;
} else {
rp->png = true;
rp->getinfo_return = BMP_RESULT_PNG;
logerr(rp->c.log, "embedded PNG data");
rp->lasterr = BMP_ERR_PNG;
return BMP_RESULT_PNG;
}
}
if (!s_is_bmptype_supported(rp))
goto abort;
rp->result_channels = 3;
if (rp->ih->bitcount <= 8) { /* indexed */
if (!(rp->palette = s_read_palette(rp)))
goto abort;
} else if (!rp->rle) { /* RGB */
if (!s_read_colormasks(rp))
goto abort;
if (rp->cmask.mask.alpha)
rp->result_channels = 4;
}
/* add alpha channel for undefined pixels in RLE bitmaps */
if (rp->rle) {
rp->result_channels = (rp->undefined_mode == BMP_UNDEFINED_TO_ALPHA) ? 4 : 3;
}
if (rp->is_icon)
rp->result_channels = 4;
if (!br_set_resultbits(rp))
goto abort;
if (rp->insanity_limit && rp->result_size > rp->insanity_limit) {
logerr(rp->c.log, "file is insanely large");
rp->lasterr = BMP_ERR_INSANE;
rp->getinfo_return = BMP_RESULT_INSANE;
} else {
rp->getinfo_return = BMP_RESULT_OK;
}
rp->read_state = RS_HEADER_OK;
return rp->getinfo_return;
abort:
rp->read_state = RS_FATAL;
rp->getinfo_return = BMP_RESULT_ERROR;
return BMP_RESULT_ERROR;
}
/*****************************************************************************
* bmpread_image_type
*****************************************************************************/
API BMPIMAGETYPE bmpread_image_type(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_IMAGETYPE_NONE;
if (rp->read_state < RS_HEADER_OK) {
logerr(rp->c.log, "Must load info, first.");
return BMP_IMAGETYPE_NONE;
}
switch (rp->fh->type) {
case BMPFILE_BM: return BMP_IMAGETYPE_BM;
case BMPFILE_BA: return BMP_IMAGETYPE_BA;
case BMPFILE_IC: return BMP_IMAGETYPE_IC;
case BMPFILE_PT: return BMP_IMAGETYPE_PT;
case BMPFILE_CI: return BMP_IMAGETYPE_CI;
case BMPFILE_CP: return BMP_IMAGETYPE_CP;
default:
break;
}
return BMP_IMAGETYPE_NONE;
}
/*****************************************************************************
* bmpread_set_64bit_conv
*****************************************************************************/
API BMPRESULT bmpread_set_64bit_conv(BMPHANDLE h, enum Bmpconv64 conv)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state >= RS_LOAD_STARTED) {
logerr(rp->c.log, "Too late to set 64bit conversion");
return BMP_RESULT_ERROR;
}
switch (conv) {
case BMP_CONV64_SRGB:
case BMP_CONV64_LINEAR:
rp->conv64 = conv;
rp->conv64_explicit = true;
break;
case BMP_CONV64_NONE:
if (rp->result_format_explicit && rp->result_format != BMP_FORMAT_S2_13) {
logerr(rp->c.log, "64-bit conversion %s imcompatible with chosen number format %s.\n",
cm_conv64_name(conv), cm_format_name(rp->result_format));
rp->lasterr = BMP_ERR_CONV64;
return BMP_RESULT_ERROR;
} else {
rp->result_format = BMP_FORMAT_S2_13;
rp->result_format_explicit = true;
}
rp->conv64 = BMP_CONV64_LINEAR;
rp->conv64_explicit = true;
break;
default:
logerr(rp->c.log, "Unknown 64-bit conversion %s (%d)", cm_conv64_name(conv), (int) conv);
rp->lasterr = BMP_ERR_CONV64;
return BMP_RESULT_ERROR;
}
return BMP_RESULT_OK;
}
/*****************************************************************************
* bmpread_is_64bit
*****************************************************************************/
API int bmpread_is_64bit(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
if (rp->ih->bitcount == 64)
return 1;
return 0;
}
/*****************************************************************************
* bmpread_iccprofile_size
*****************************************************************************/
API size_t bmpread_iccprofile_size(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
if (rp->ih->cstype == PROFILE_EMBEDDED && rp->ih->profilesize <= MAX_ICCPROFILE_SIZE) {
rp->iccprofile_size_queried = true;
return (size_t)rp->ih->profilesize;
}
return 0;
}
/********************************************************
* bmpread_load_iccprofile
*******************************************************/
API BMPRESULT bmpread_load_iccprofile(BMPHANDLE h, unsigned char **profile)
{
BMPREAD rp;
size_t memsize;
long pos;
bool we_allocated = false;
bool file_messed_up = false;
if (!(rp = cm_read_handle(h)))
goto abort;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY) {
logerr(rp->c.log, "Must load info before loading ICC profile");
goto abort;
}
if (!rp->iccprofile_size_queried) {
logerr(rp->c.log, "Must query profile size before loading ICC profile");
goto abort;
}
if (rp->ih->cstype != PROFILE_EMBEDDED) {
logerr(rp->c.log, "Image has no ICC profile");
goto abort;
}
if (rp->ih->profilesize > MAX_ICCPROFILE_SIZE) {
logerr(rp->c.log, "ICC profile is too large (%lu). Max is %lu",
(unsigned long) rp->ih->profilesize,
(unsigned long) MAX_ICCPROFILE_SIZE);
goto abort;
}
if (!profile) {
logerr(rp->c.log, "profile is NULL");
goto abort;
}
memsize = rp->ih->profilesize;
if (!*profile) {
if (!(*profile = malloc(memsize))) {
logsyserr(rp->c.log, "allocating ICC profile");
goto abort;
}
we_allocated = true;
}
memset(*profile, 0, memsize);
if (-1 == (pos = ftell(rp->file))) {
logsyserr(rp->c.log, "reading current file position");
goto abort;
}
if (fseek(rp->file, rp->ih->profiledata + 14, SEEK_SET)) {
logsyserr(rp->c.log, "seeking ICC profile in file");
goto abort;
}
/* Any failure from here on out cannot be reasonably recovered from, as
* the file position will be messed up! */
file_messed_up = true;
if (memsize != fread(*profile, 1, memsize, rp->file)) {
if (feof(rp->file))
logerr(rp->c.log, "EOF while reading ICC profile");
else
logsyserr(rp->c.log, "reading ICC profile");
goto abort;
}
if (fseek(rp->file, pos, SEEK_SET)) {
logsyserr(rp->c.log, "failed to reset file position after reading ICC profile");
goto abort;
}
return BMP_RESULT_OK;
abort:
if (profile && *profile && we_allocated) {
free(*profile);
*profile = NULL;
}
if (file_messed_up)
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
/*****************************************************************************
* bmpread_dimensions
*****************************************************************************/
API BMPRESULT bmpread_dimensions(BMPHANDLE h, int* restrict width,
int* restrict height,
int* restrict channels,
int* restrict bitsperchannel,
enum BmpOrient* restrict orientation)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state < RS_HEADER_OK)
bmpread_load_info((BMPHANDLE)(void*)rp);
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
if (width) {
*width = rp->width;
rp->dim_queried_width = true;
}
if (height) {
*height = rp->height;
rp->dim_queried_height = true;
}
if (channels) {
*channels = rp->result_channels;
rp->dim_queried_channels = true;
}
if (bitsperchannel) {
*bitsperchannel = rp->result_bitsperchannel;
rp->dim_queried_bitsperchannel = true;
}
if (orientation) {
*orientation = rp->orientation;
}
if (rp->dim_queried_width && rp->dim_queried_height &&
rp->dim_queried_channels && rp->dim_queried_bitsperchannel) {
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
}
return rp->getinfo_return;
}
/*****************************************************************************
* br_set_number_format
*****************************************************************************/
BMPRESULT br_set_number_format(BMPREAD_R rp, enum BmpFormat format)
{
if (rp->result_format == format) {
rp->result_format_explicit = true;
return BMP_RESULT_OK;
}
if (rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
switch (format) {
case BMP_FORMAT_INT:
/* always ok */
break;
case BMP_FORMAT_FLOAT:
case BMP_FORMAT_S2_13:
if (rp->result_indexed) {
logerr(rp->c.log, "Cannot load color index as float or s2.13");
rp->lasterr = BMP_ERR_FORMAT;
return BMP_RESULT_ERROR;
}
if (rp->is_icon) {
logerr(rp->c.log, "Cannot load icons/pointers as float or s2.13");
rp->lasterr = BMP_ERR_FORMAT;
return BMP_RESULT_ERROR;
}
break;
default:
logerr(rp->c.log, "Invalid number format (%d) specified", (int) format);
rp->lasterr = BMP_ERR_FORMAT;
return BMP_RESULT_ERROR;
}
rp->result_format = format;
rp->result_format_explicit = true;
if (!br_set_resultbits(rp)) {
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
return BMP_RESULT_OK;
}
/*****************************************************************************
* bmpread_* getters for single dimension values
*****************************************************************************/
enum Dimint {
DIM_WIDTH = 1,
DIM_HEIGHT,
DIM_CHANNELS,
DIM_BITS_PER_CHANNEL,
DIM_ORIENTATION,
DIM_XDPI,
DIM_YDPI,
};
static int s_single_dim_val(BMPHANDLE h, enum Dimint dim);
API int bmpread_width(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_WIDTH); }
API int bmpread_height(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_HEIGHT); }
API int bmpread_channels(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_CHANNELS); }
API int bmpread_bitsperchannel(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_BITS_PER_CHANNEL); }
API int bmpread_bits_per_channel(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_BITS_PER_CHANNEL); }
API int bmpread_topdown(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_ORIENTATION); }
API enum BmpOrient bmpread_orientation(BMPHANDLE h)
{ return (enum BmpOrient) s_single_dim_val(h, DIM_ORIENTATION); }
API int bmpread_resolution_xdpi(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_XDPI); }
API int bmpread_resolution_ydpi(BMPHANDLE h)
{ return s_single_dim_val(h, DIM_YDPI); }
/*****************************************************************************
* s_single_dim_val
*****************************************************************************/
static int s_single_dim_val(BMPHANDLE h, enum Dimint dim)
{
BMPREAD rp;
int ret;
if (!(rp = cm_read_handle(h)))
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
switch (dim) {
case DIM_WIDTH:
rp->dim_queried_width = true;
ret = rp->width;
break;
case DIM_HEIGHT:
rp->dim_queried_height = true;
ret = rp->height;
break;
case DIM_CHANNELS:
rp->dim_queried_channels = true;
ret = rp->result_channels;
break;
case DIM_BITS_PER_CHANNEL:
rp->dim_queried_bitsperchannel = true;
ret = rp->result_bitsperchannel;
break;
case DIM_ORIENTATION:
ret = (int) rp->orientation;
break;
case DIM_XDPI:
ret = (int) (rp->ih->xpelspermeter / (100.0 / 2.54) + 0.5);
break;
case DIM_YDPI:
ret = (int) (rp->ih->ypelspermeter / (100.0 / 2.54) + 0.5);
break;
default:
return 0;
}
if (rp->dim_queried_width && rp->dim_queried_height &&
rp->dim_queried_channels && rp->dim_queried_bitsperchannel) {
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
}
return ret;
}
/*****************************************************************************
* bmpread_buffersize
*****************************************************************************/
API size_t bmpread_buffersize(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
return rp->result_size;
}
/*****************************************************************************
* bmpread_set_insanity_limit
*****************************************************************************/
API void bmpread_set_insanity_limit(BMPHANDLE h, size_t limit)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return;
rp->insanity_limit = limit;
if (rp->read_state < RS_HEADER_OK)
return;
if (rp->getinfo_return == BMP_RESULT_INSANE) {
if (limit == 0 || limit >= rp->result_size)
rp->getinfo_return = BMP_RESULT_OK;
} else if (rp->getinfo_return == BMP_RESULT_OK) {
if (limit > 0 && rp->result_size > limit)
rp->getinfo_return = BMP_RESULT_INSANE;
}
}
/*****************************************************************************
* bmpread_set_undefined
*****************************************************************************/
API void bmpread_set_undefined_to_alpha(BMPHANDLE h, int mode)
{ bmpread_set_undefined(h, (enum BmpUndefined) mode); }
API void bmpread_set_undefined(BMPHANDLE h, enum BmpUndefined mode)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return;
if (rp->is_icon && mode != BMP_UNDEFINED_LEAVE) {
logerr(rp->c.log, "For icons/pointers, only BMP_UNDEFINED_LEAVE is valid.");
rp->lasterr = BMP_ERR_UNDEFMODE;
mode = BMP_UNDEFINED_LEAVE;
}
if (mode == rp->undefined_mode)
return;
if (mode != BMP_UNDEFINED_TO_ALPHA && mode != BMP_UNDEFINED_LEAVE) {
logerr(rp->c.log, "Invalid undefined-mode selected");
rp->lasterr = BMP_ERR_UNDEFMODE;
return;
}
rp->undefined_mode = mode;
/* we are changing the setting after dimensions have */
/* been established. Only relevant for RLE-encoding */
if (!rp->rle)
return;
rp->result_channels = (mode == BMP_UNDEFINED_TO_ALPHA) ? 4 : 3;
rp->read_state = MIN(RS_HEADER_OK, rp->read_state);
rp->dim_queried_channels = false;
if (!br_set_resultbits(rp)) {
rp->lasterr = BMP_ERR_DIMENSIONS;
rp->read_state = RS_FATAL;
}
}
/*****************************************************************************
* br_free
*****************************************************************************/
void br_free(BMPREAD rp)
{
rp->c.magic = 0;
if (rp->is_arrayimg)
return;
if (rp->arrayimgs) {
for (int i = 0; i < rp->narrayimgs; i++) {
((BMPREAD)rp->arrayimgs[i].handle)->is_arrayimg = false;
br_free((BMPREAD)rp->arrayimgs[i].handle);
}
free(rp->arrayimgs);
}
if (rp->icon_mono_and)
free(rp->icon_mono_and);
if (rp->icon_mono_xor)
free(rp->icon_mono_xor);
if (rp->palette)
free(rp->palette);
if (rp->ih)
free(rp->ih);
if (rp->fh)
free(rp->fh);
if (rp->c.log)
logfree(rp->c.log);
free(rp);
}
/*****************************************************************************
* s_is_bmptype_supported
*****************************************************************************/
static bool s_is_bmptype_supported_rgb(BMPREAD_R rp);
static bool s_is_bmptype_supported_indexed(BMPREAD_R rp);
static bool s_is_bmptype_supported(BMPREAD_R rp)
{
if (rp->ih->planes != 1) {
logerr(rp->c.log, "Unsupported number of planes (%d). Must be 1.", (int) rp->ih->planes);
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
if (rp->is_icon) {
if (rp->ih->compression != BI_RGB &&
rp->ih->compression != BI_RLE4 &&
rp->ih->compression != BI_RLE8 &&
rp->ih->compression != BI_OS2_RLE24) {
logerr(rp->c.log, "Unsupported compression %s for icon/pointer",
s_compression_name(rp->ih->compression));
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
if (rp->ih->bitcount > 32) {
logerr(rp->c.log, "Unsupported bitcount %d for icon/pointer",
(int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
if (rp->ih->version > BMPINFO_OS22) {
logerr(rp->c.log, "Unsupported header version %s for icon/pointer",
cm_infoheader_name(rp->ih->version));
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
if (rp->result_format != BMP_FORMAT_INT) {
logerr(rp->c.log, "Chosen number format %s is incompatible with icon/pointer",
cm_format_name(rp->result_format));
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
}
if (rp->ih->bitcount <= 8)
return s_is_bmptype_supported_indexed(rp);
else
return s_is_bmptype_supported_rgb(rp);
return true;
}
/*****************************************************************************
* s_is_bmptype_supported_rgb
*****************************************************************************/
static bool s_is_bmptype_supported_rgb(BMPREAD_R rp)
{
switch (rp->ih->bitcount) {
case 16:
case 24:
case 32:
case 64:
/* ok */
break;
default:
logerr(rp->c.log, "Invalid bitcount %d for RGB image", (int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_HEADER;
return false;
}
switch (rp->ih->compression) {
case BI_RGB:
/* ok */
break;
case BI_BITFIELDS:
case BI_ALPHABITFIELDS:
if (rp->ih->bitcount == 64) {
logerr(rp->c.log, "Invalid bitcount %d for BITFIELDS", (int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_HEADER;
return false;
}
break;
case BI_OS2_RLE24:
if (rp->ih->bitcount != 24) {
logerr(rp->c.log, "Invalid bitcount %d for RLE24 compression", (int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_HEADER;
return false;
}
break;
default:
logerr(rp->c.log, "Unsupported compression %s for RGB image",
s_compression_name(rp->ih->compression));
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
return true;
}
/*****************************************************************************
* s_is_bmptype_supported_indexed
*****************************************************************************/
static bool s_is_bmptype_supported_indexed(BMPREAD_R rp)
{
switch (rp->ih->bitcount) {
case 1:
case 2:
case 4:
case 8:
/* ok */
break;
default:
logerr(rp->c.log, "Invalid bitcount %d for indexed image",
(int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_HEADER;
return false;
}
switch (rp->ih->compression) {
case BI_RGB:
case BI_RLE4:
case BI_RLE8:
case BI_OS2_HUFFMAN:
if ( (rp->ih->compression == BI_RLE4 && rp->ih->bitcount != 4) ||
(rp->ih->compression == BI_RLE8 && rp->ih->bitcount != 8) ||
(rp->ih->compression == BI_OS2_HUFFMAN && rp->ih->bitcount != 1)) {
logerr(rp->c.log, "Unsupported compression %s for %d-bit data",
s_compression_name(rp->ih->compression),
(int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
/* ok */
break;
default:
logerr(rp->c.log, "Unsupported compression %s for indexed image",
s_compression_name(rp->ih->compression));
rp->lasterr = BMP_ERR_UNSUPPORTED;
return false;
}
return true;
}
/*****************************************************************************
* s_read_palette
*****************************************************************************/
static struct Palette* s_read_palette(BMPREAD_R rp)
{
int i, r,g,b;
struct Palette *palette;
size_t memsize;
int bytes_per_entry;
int colors_in_file;
int max_colors_in_file;
int colors_full_palette;
int colors_ignore = 0;
if (rp->ih->clrused > INT_MAX || rp->ih->clrimportant > rp->ih->clrused) {
logerr(rp->c.log, "Unreasonable color numbers for palette (%lu/%lu)",
(unsigned long) rp->ih->clrused,
(unsigned long) rp->ih->clrimportant);
rp->lasterr = BMP_ERR_INVALID;
return NULL;
}
if (rp->fh->offbits - rp->bytes_read > INT_MAX) {
logerr(rp->c.log, "gap to pixeldata too big (%lu)",
(unsigned long) rp->fh->offbits - rp->bytes_read);
rp->lasterr = BMP_ERR_INVALID;
return NULL;
}
if (rp->fh->offbits < rp->bytes_read) {
logerr(rp->c.log, "Invalid offset to pixel data");
rp->lasterr = BMP_ERR_INVALID;
return NULL;
}
bytes_per_entry = rp->ih->version == BMPINFO_CORE_OS21 ? 3 : 4;
max_colors_in_file = (rp->fh->offbits - rp->bytes_read) / bytes_per_entry;
colors_full_palette = 1 << rp->ih->bitcount;
if (0 == (colors_in_file = rp->ih->clrused)) {
colors_in_file = MIN(colors_full_palette, max_colors_in_file);
} else if (colors_in_file > max_colors_in_file) {
logerr(rp->c.log, "given palette size (%d) too large for available data (%d)",
colors_in_file, max_colors_in_file);
rp->lasterr = BMP_ERR_INVALID;
return NULL;
}
if (colors_in_file > colors_full_palette)
colors_ignore = colors_in_file - colors_full_palette;
memsize = sizeof *palette +
(colors_in_file - colors_ignore) * sizeof palette->color[0];
if (!(palette = malloc(memsize))) {
logsyserr(rp->c.log, "Allocating mem for palette");
rp->lasterr = BMP_ERR_MEMORY;
return NULL;
}
memset(palette, 0, memsize);
palette->numcolors = colors_in_file - colors_ignore;
for (i = 0; i < palette->numcolors; i++) {
if (EOF == (b = getc(rp->file)) ||
EOF == (g = getc(rp->file)) ||
EOF == (r = getc(rp->file)) ||
((bytes_per_entry == 4) && (EOF == getc(rp->file))) ) {
if (feof(rp->file)) {
logerr(rp->c.log, "file ended reading palette entries");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "reading palette entries");
rp->lasterr = BMP_ERR_FILEIO;
}
free (palette);
return NULL;
}
rp->bytes_read += bytes_per_entry;
palette->color[i].red = r;
palette->color[i].green = g;
palette->color[i].blue = b;
}
for (i = 0; i < colors_ignore; i++) {
if (!cm_gobble_up(rp, bytes_per_entry)) {
logerr(rp->c.log, "reading superfluous palette entries");
free(palette);
return NULL;
}
rp->bytes_read += bytes_per_entry;
}
return palette;
}
/*****************************************************************************
* br_set_resultbits
*****************************************************************************/
static bool s_check_dimensions(BMPREAD_R rp);
bool br_set_resultbits(BMPREAD_R rp)
{
int newbits, max_bits = 0, i;
if (!rp->ih->bitcount)
return true;
switch (rp->result_format) {
case BMP_FORMAT_FLOAT:
if (rp->result_indexed) {
logerr(rp->c.log, "Float is invalid number format for indexed image\n");
rp->lasterr = BMP_ERR_FORMAT;
return false;
}
newbits = 8 * sizeof (float);
break;
case BMP_FORMAT_S2_13:
if (rp->result_indexed) {
logerr(rp->c.log, "s2.13 is invalid number format for indexed image\n");
rp->lasterr = BMP_ERR_FORMAT;
return false;
}
newbits = 16;
break;
case BMP_FORMAT_INT:
if (rp->ih->bitcount <= 8 || rp->rle)
newbits = 8;
else { /* RGB */
for (i = 0; i < 4; i++) {
max_bits = MAX(max_bits, rp->cmask.bits.value[i]);
}
newbits = 8;
while (newbits < max_bits && newbits < 32) {
newbits *= 2;
}
}
break;
default:
logerr(rp->c.log, "Invalid number format %d\n", rp->result_format);
rp->lasterr = BMP_ERR_FORMAT;
return false;
}
if (newbits != rp->result_bitsperchannel) {
rp->dim_queried_bitsperchannel = false;
rp->read_state = MIN(RS_HEADER_OK, rp->read_state);
}
rp->result_bitsperchannel = newbits;
rp->result_bits_per_pixel = rp->result_bitsperchannel * rp->result_channels;
rp->result_bytes_per_pixel = rp->result_bits_per_pixel / 8;
if (!s_check_dimensions(rp))
return false;
rp->result_size = (size_t) rp->width * rp->height * rp->result_bytes_per_pixel;
if (rp->read_state >= RS_HEADER_OK) {
if (rp->insanity_limit && rp->result_size > rp->insanity_limit) {
if (rp->getinfo_return == BMP_RESULT_OK) {
logerr(rp->c.log, "file is insanely large");
rp->lasterr = BMP_ERR_INSANE;
rp->getinfo_return = BMP_RESULT_INSANE;
}
} else {
if (rp->getinfo_return == BMP_RESULT_INSANE)
rp->getinfo_return = BMP_RESULT_OK;
}
}
return true;
}
/*****************************************************************************
* s_check_dimensions
*****************************************************************************/
static bool s_check_dimensions(BMPREAD_R rp)
{
uint64_t npixels;
size_t maxpixels;
npixels = (uint64_t) rp->width * rp->height;
maxpixels = SIZE_MAX / rp->result_bytes_per_pixel;
if (npixels > maxpixels || rp->width < 1 || rp->height < 1) {
logerr(rp->c.log, "Invalid BMP dimensions (%dx%d)", rp->width, rp->height);
rp->lasterr = BMP_ERR_DIMENSIONS;
rp->read_state = RS_FATAL;
return false;
}
return true;
}
/*****************************************************************************
* s_read_colormasks
*****************************************************************************/
static bool s_read_masks_from_bitfields(BMPREAD_R rp);
static bool s_create_implicit_colormasks(BMPREAD_R rp);
static inline int s_calc_bits_for_mask(unsigned long long mask);
static inline int s_calc_shift_for_mask(unsigned long long mask);
static bool s_read_colormasks(BMPREAD_R rp)
{
int i, max_bits = 0, sum_bits = 0;
switch (rp->ih->compression) {
case BI_BITFIELDS:
case BI_ALPHABITFIELDS:
if (!s_read_masks_from_bitfields(rp))
return false;
break;
case BI_RGB:
if (!s_create_implicit_colormasks(rp))
return false;
break;
default:
logerr(rp->c.log, "Invalid compression (%s)",
s_compression_name(rp->ih->compression));
rp->lasterr = BMP_ERR_INVALID;
return false;
}
if (rp->cmask.mask.alpha) {
rp->has_alpha = true;
rp->result_channels = 4;
} else {
rp->has_alpha = false;
rp->result_channels = 3;
}
for (i = 0; i < 4; i++) {
max_bits = MAX(max_bits, rp->cmask.bits.value[i]);
sum_bits += rp->cmask.bits.value[i];
}
if (max_bits > MIN(rp->ih->bitcount, 32) || sum_bits > rp->ih->bitcount) {
logerr(rp->c.log, "Invalid mask bitcount (max=%d, sum=%d)",
max_bits, sum_bits);
rp->lasterr = BMP_ERR_INVALID;
return false;
}
if (!(rp->cmask.mask.red | rp->cmask.mask.green | rp->cmask.mask.blue)) {
logerr(rp->c.log, "Empty color masks. Corrupt BMP?");
rp->lasterr = BMP_ERR_INVALID;
return false;
}
if (rp->cmask.mask.red & rp->cmask.mask.green &
rp->cmask.mask.blue & rp->cmask.mask.alpha) {
logerr(rp->c.log, "Overlapping color masks. Corrupt BMP?");
rp->lasterr = BMP_ERR_INVALID;
return false;
}
return true;
}
/*****************************************************************************
* s_read_masks_from_bitfields
*****************************************************************************/
static bool s_read_masks_from_bitfields(BMPREAD_R rp)
{
uint32_t r,g,b,a;
int i;
if (!(rp->ih->bitcount == 16 || rp->ih->bitcount == 32)) {
logerr(rp->c.log, "Invalid bitcount (%d) for BI_BITFIELDS."
"Must be 16 or 32", (int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_INVALID;
return false;
}
if (rp->ih->version < BMPINFO_V3_ADOBE1) {
if (!(read_u32_le(rp->file, &r) &&
read_u32_le(rp->file, &g) &&
read_u32_le(rp->file, &b))) {
if (feof(rp->file)) {
logerr(rp->c.log, "File ended reading color masks");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "Reading BMP color masks");
rp->lasterr = BMP_ERR_FILEIO;
}
return false;
}
rp->bytes_read += 12;
rp->cmask.mask.red = r;
rp->cmask.mask.green = g;
rp->cmask.mask.blue = b;
if (rp->ih->compression == BI_ALPHABITFIELDS) {
if (!read_u32_le(rp->file, &a)) {
if (feof(rp->file)) {
logerr(rp->c.log, "File ended reading color masks");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "Reading BMP color masks");
rp->lasterr = BMP_ERR_FILEIO;
}
return false;
}
rp->bytes_read += 4;
rp->cmask.mask.alpha = a;
}
} else {
rp->cmask.mask.red = rp->ih->redmask;
rp->cmask.mask.green = rp->ih->greenmask;
rp->cmask.mask.blue = rp->ih->bluemask;
if (rp->ih->version >= BMPINFO_V3_ADOBE2)
rp->cmask.mask.alpha = rp->ih->alphamask;
}
for (i = 0; i < (rp->cmask.mask.alpha ? 4 : 3); i++) {
rp->cmask.bits.value[i] = s_calc_bits_for_mask(rp->cmask.mask.value[i]);
rp->cmask.shift.value[i] = s_calc_shift_for_mask(rp->cmask.mask.value[i]);
}
return true;
}
/*****************************************************************************
* s_create_implicit_colormasks
*****************************************************************************/
static bool s_create_implicit_colormasks(BMPREAD_R rp)
{
int i, bitsperchannel;
switch (rp->ih->bitcount) {
case 16:
bitsperchannel = 5;
break;
case 24:
case 32:
bitsperchannel = 8;
break;
case 64:
bitsperchannel = 16;
break;
default:
logerr(rp->c.log, "Invalid bitcount for BMP (%d)", (int) rp->ih->bitcount);
rp->lasterr = BMP_ERR_INVALID;
return false;
}
for (i = 0; i < 3; i++) {
rp->cmask.shift.value[i] = (2-i) * bitsperchannel;
rp->cmask.mask.value[i] =
((1ULL<<bitsperchannel)-1) << rp->cmask.shift.value[i];
rp->cmask.bits.value[i] = s_calc_bits_for_mask(rp->cmask.mask.value[i]);
}
if (rp->ih->bitcount == 64) {
rp->cmask.shift.alpha = 3 * bitsperchannel;
rp->cmask.mask.alpha =
((1ULL<<bitsperchannel)-1) << rp->cmask.shift.alpha;
rp->cmask.bits.alpha = s_calc_bits_for_mask(rp->cmask.mask.alpha);
}
return true;
}
/*****************************************************************************
* s_calc_bits_for_mask
*****************************************************************************/
static inline int s_calc_bits_for_mask(unsigned long long mask)
{
int bits = 0;
if (!mask)
return 0;
while (0 == (mask & 0x01))
mask >>= 1;
while (1 == (mask & 0x01)) {
bits++;
mask >>= 1;
}
return bits;
}
/*****************************************************************************
* s_calc_shift_for_mask
*****************************************************************************/
static inline int s_calc_shift_for_mask(unsigned long long mask)
{
int shift = 0;
if (!mask)
return 0;
while (0 == (mask & 0x01)) {
shift++;
mask >>= 1;
}
return shift;
}
/*****************************************************************************
* s_read_file_header
*****************************************************************************/
static bool s_read_file_header(BMPREAD_R rp)
{
if (read_u16_le(rp->file, &rp->fh->type) &&
read_u32_le(rp->file, &rp->fh->size) &&
read_u16_le(rp->file, &rp->fh->reserved1) &&
read_u16_le(rp->file, &rp->fh->reserved2) &&
read_u32_le(rp->file, &rp->fh->offbits) ) {
rp->bytes_read += 14;
return true;
}
if (feof(rp->file)) {
logerr(rp->c.log, "unexpected end-of-file while reading "
"file header");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "error reading file header");
rp->lasterr = BMP_ERR_FILEIO;
}
return false;
}
/*****************************************************************************
* s_read_info_header
*****************************************************************************/
static void s_detect_os2_header(BMPREAD_R rp);
static bool s_read_info_header(BMPREAD_R rp)
{
int skip, i, filepos;
unsigned char buf[124];
size_t read_size;
filepos = (int) rp->bytes_read;
if (!read_u32_le(rp->file, &rp->ih->size))
goto abort_file_err;
rp->bytes_read += 4;
if (rp->ih->size > INT_MAX) {
logerr(rp->c.log, "Ridiculous info header size (%lu)", (unsigned long) rp->ih->size);
return false;
}
switch (rp->ih->size) {
case 12: rp->ih->version = BMPINFO_CORE_OS21; break;
case 16:
case 20:
case 24:
case 28:
case 32:
case 36:
case 42:
case 44:
case 46:
case 48:
case 60:
case 64: rp->ih->version = BMPINFO_OS22; break;
case 40: rp->ih->version = BMPINFO_V3; break;
case 52: rp->ih->version = BMPINFO_V3_ADOBE1; break;
case 56: rp->ih->version = BMPINFO_V3_ADOBE2; break;
case 108: rp->ih->version = BMPINFO_V4; break;
case 124: rp->ih->version = BMPINFO_V5; break;
default:
if (rp->ih->size > 124)
rp->ih->version = BMPINFO_FUTURE;
else {
logerr(rp->c.log, "Invalid info header size (%lu)",
(unsigned long) rp->ih->size);
rp->lasterr = BMP_ERR_HEADER;
return false;
}
break;
}
memset(buf, 0, sizeof buf);
read_size = MIN(sizeof buf - 4, rp->ih->size - 4);
if (fread(buf + 4, 1, read_size, rp->file) != read_size)
goto abort_file_err;
rp->bytes_read += read_size;
if (rp->ih->version == BMPINFO_CORE_OS21) {
rp->ih->width = u16_from_le(buf + 4);
rp->ih->height = u16_from_le(buf + 6);
rp->ih->planes = u16_from_le(buf + 8);
rp->ih->bitcount = u16_from_le(buf + 10);
goto header_done;
}
rp->ih->width = s32_from_le(buf + 4);
rp->ih->height = s32_from_le(buf + 8);
rp->ih->planes = u16_from_le(buf + 12);
rp->ih->bitcount = u16_from_le(buf + 14);
if (rp->ih->size == 16) { /* 16-byte BMPINFO_OS22 */
goto header_done;
}
rp->ih->compression = u32_from_le(buf + 16);
rp->ih->sizeimage = u32_from_le(buf + 20);
rp->ih->xpelspermeter = s32_from_le(buf + 24);
rp->ih->ypelspermeter = s32_from_le(buf + 28);
rp->ih->clrused = u32_from_le(buf + 32);
rp->ih->clrimportant = u32_from_le(buf + 36);
if (rp->ih->version == BMPINFO_OS22) {
rp->ih->resolution = u16_from_le(buf + 40);
rp->ih->orientation = u16_from_le(buf + 44);
rp->ih->halftone_alg = u16_from_le(buf + 46);
rp->ih->halftone_parm1 = u32_from_le(buf + 48);
rp->ih->halftone_parm2 = u32_from_le(buf + 52);
rp->ih->color_encoding = u32_from_le(buf + 56);
rp->ih->app_id = u32_from_le(buf + 60);
goto header_done;
}
/* BITMAPV4HEADER */
rp->ih->redmask = u32_from_le(buf + 40);
rp->ih->greenmask = u32_from_le(buf + 44);
rp->ih->bluemask = u32_from_le(buf + 48);
rp->ih->alphamask = u32_from_le(buf + 52);
rp->ih->cstype = u32_from_le(buf + 56);
rp->ih->redX = s32_from_le(buf + 60);
rp->ih->redY = s32_from_le(buf + 64);
rp->ih->redZ = s32_from_le(buf + 68);
rp->ih->greenX = s32_from_le(buf + 72);
rp->ih->greenY = s32_from_le(buf + 76);
rp->ih->greenZ = s32_from_le(buf + 80);
rp->ih->blueX = s32_from_le(buf + 84);
rp->ih->blueY = s32_from_le(buf + 88);
rp->ih->blueZ = s32_from_le(buf + 92);
rp->ih->gammared = u32_from_le(buf + 96);
rp->ih->gammagreen = u32_from_le(buf + 100);
rp->ih->gammablue = u32_from_le(buf + 104);
/* BITMAPV5HEADER */
rp->ih->intent = u32_from_le(buf + 108);
rp->ih->profiledata = u32_from_le(buf + 112);
rp->ih->profilesize = u32_from_le(buf + 116);
rp->ih->reserved = u32_from_le(buf + 120);
header_done:
/* read past bigger info headers: */
skip = (int) rp->ih->size - ((int) rp->bytes_read - filepos);
for (i = 0; i < skip; i++) {
if (EOF == getc(rp->file))
goto abort_file_err;
rp->bytes_read++;
}
s_detect_os2_header(rp);
return true;
abort_file_err:
if (feof(rp->file)) {
logerr(rp->c.log, "Unexpected end of file while reading BMP info header");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "While reading BMP info header");
rp->lasterr = BMP_ERR_FILEIO;
}
return false;
}
/*****************************************************************************
* s_detect_os2_header
*****************************************************************************/
static void s_detect_os2_header(BMPREAD_R rp)
{
if (rp->ih->version == BMPINFO_V3) {
/* might actually be a 40-byte OS/2 header */
if (rp->fh->size == 54 ||
(rp->ih->compression == BI_OS2_HUFFMAN_DUP && rp->ih->bitcount == 1) ||
(rp->ih->compression == BI_OS2_RLE24_DUP && rp->ih->bitcount == 24)) {
rp->ih->version = BMPINFO_OS22;
} else if (rp->fh->type != BMPFILE_BM) {
/* arrays, icons, and pointers are always OS/2 */
rp->ih->version = BMPINFO_OS22;
}
}
if (rp->ih->version <= BMPINFO_OS22) {
if (rp->ih->compression == BI_OS2_HUFFMAN_DUP)
rp->ih->compression = BI_OS2_HUFFMAN;
else if (rp->ih->compression == BI_OS2_RLE24_DUP)
rp->ih->compression = BI_OS2_RLE24;
}
}
/*****************************************************************************
* bmpread_info_* int
*****************************************************************************/
enum Infoint {
INFO_INT_HEADER_VERSION,
INFO_INT_HEADER_SIZE,
INFO_INT_COMPRESSION,
INFO_INT_BITCOUNT,
};
static int s_info_int(BMPHANDLE h, enum Infoint info);
API enum BmpInfoVer bmpread_info_header_version(BMPHANDLE h)
{ return (enum BmpInfoVer) s_info_int(h, INFO_INT_HEADER_VERSION); }
API int bmpread_info_header_size(BMPHANDLE h)
{ return s_info_int(h, INFO_INT_HEADER_SIZE); }
API int bmpread_info_compression(BMPHANDLE h)
{ return s_info_int(h, INFO_INT_COMPRESSION); }
API int bmpread_info_bitcount(BMPHANDLE h)
{ return s_info_int(h, INFO_INT_BITCOUNT); }
/*****************************************************************************
* s_info_int
*****************************************************************************/
static int s_info_int(BMPHANDLE h, enum Infoint info)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
switch (info) {
case INFO_INT_HEADER_VERSION:
return (int) rp->ih->version;
case INFO_INT_HEADER_SIZE:
return (int) rp->ih->size;
case INFO_INT_COMPRESSION:
return (int) rp->ih->compression;
case INFO_INT_BITCOUNT:
return (int) rp->ih->bitcount;
}
return 0;
}
/*****************************************************************************
* bmpread_info_* str
*****************************************************************************/
enum Infostr {
INFO_STR_HEADER_NAME,
INFO_STR_COMPRESSION_NAME,
};
static const char* s_info_str(BMPHANDLE h, enum Infostr info);
API const char* bmpread_info_header_name(BMPHANDLE h)
{ return s_info_str(h, INFO_STR_HEADER_NAME); }
API const char* bmpread_info_compression_name(BMPHANDLE h)
{ return s_info_str(h, INFO_STR_COMPRESSION_NAME); }
/*****************************************************************************
* s_info_str
*****************************************************************************/
static const char* s_info_str(BMPHANDLE h, enum Infostr info)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return "";
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_FATAL)
return "";
switch (info) {
case INFO_STR_HEADER_NAME:
return cm_infoheader_name(rp->ih->version);
case INFO_STR_COMPRESSION_NAME:
return s_compression_name(rp->ih->compression);
}
return "";
}
/*****************************************************************************
* bmpread_info_channel_bits
*****************************************************************************/
API BMPRESULT bmpread_info_channel_bits(BMPHANDLE h, int *r, int *g, int *b, int *a)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
if (rp->ih->compression == BI_OS2_RLE24) {
*r = *g = *b = 8;
*a = 0;
} else if (rp->ih->bitcount <= 8) {
*r = *g = *b = *a = 0;
} else {
*r = rp->cmask.bits.red;
*g = rp->cmask.bits.green;
*b = rp->cmask.bits.blue;
*a = rp->cmask.bits.alpha;
}
return BMP_RESULT_OK;
}
/*****************************************************************************
* s_compression_name
*****************************************************************************/
const char* s_compression_name(int compression)
{
static char buff[100];
switch (compression) {
case BI_RGB : return "BI_RGB";
case BI_RLE8 : return "BI_RLE8";
case BI_RLE4 : return "BI_RLE4";
case BI_OS2_HUFFMAN : return "BI_OS2_HUFFMAN";
case BI_OS2_RLE24 : return "BI_OS2_RLE24";
case BI_BITFIELDS : return "BI_BITFIELDS";
case BI_JPEG : return "BI_JPEG";
case BI_PNG : return "BI_PNG";
case BI_ALPHABITFIELDS : return "BI_ALPHABITFIELDS";
case BI_CMYK : return "BI_CMYK";
case BI_CMYKRLE8 : return "BI_CMYKRLE8";
case BI_CMYKRLE4 : return "BI_CMYKRLE4";
default:
snprintf(buff, sizeof buff, "unknown (%d)", compression);
}
return buff;
}