9 Commits

Author SHA1 Message Date
Rupert
1b3c8a1081 add bmpwrite_set_huffman_img_fg_idx()
add ability to specify which palette index corresponds to foreground,
essential to optimize Huffman compression.
2025-04-09 12:21:26 +02:00
Rupert
1ede644b71 s_read_indexed_line(): refactor of 32-bit buffer 2025-04-03 16:01:10 +02:00
Rupert
1b9c8af659 typo NODEBUG -> NDEBUG 2025-04-03 15:15:53 +02:00
Rupert
a4800cb684 minor changes
- add an assert
- comment typo / formatting
2025-04-02 19:30:38 +02:00
Rupert
ed07c4f618 eliminate MS typedefs 2025-04-02 18:31:33 +02:00
Rupert
9297d88483 meson.build update
huffstats.c shouldn't have been in there. Was for testing, only
2025-03-02 17:28:48 +01:00
Rupert
c0c2513c7d remove reversebits table
change huffman bit buffer orientation, so we don't need the
reversebits lookup anymore.
2025-03-02 00:25:36 +01:00
Rupert
352a231632 minor corrections in s2.13 conversion 2025-01-23 12:36:01 +01:00
Rupert
72f8085c41 s_check_dimensions() needs only be called by s_set_resultbits() 2025-01-16 22:35:08 +01:00
9 changed files with 316 additions and 411 deletions

View File

@@ -2,10 +2,10 @@
Refer to the *Quick Start Guide* (API-quick-start.md) for a quick intro to bmplib which describes only the minimal set of functions needed to read/write BMP files.
## 1. Functions for reading BMP files
### Get a handle
```
BMPHANDLE bmpread_new(FILE *file)
```
@@ -16,14 +16,14 @@ handle.
The handle cannot be reused to read multiple files.
### Read the file header
```
BMPRESULT bmpread_load_info(BMPHANDLE h)
```
bmplib reads the file header and checks validity. Possible return values are:
- `BMP_RESULT_OK`: All is good, you can proceed to read the file.
- `BMP_INSANE`: The file is valid, but huge. The default limit is 500MB
(relevant is the required buffer size to hold the complete image, not the
@@ -42,8 +42,8 @@ bmplib reads the file header and checks validity. Possible return values are:
Calling `bmpread_load_info()` is optional when you use `bmpread_dimensions()`
(see below).
### Get image dimensions
```
BMPRESULT bmpread_dimensions(BMPHANDLE h,
int *width,
@@ -77,30 +77,32 @@ BMPORIENT bmpread_orientation(BMPHANDLE h)
int bmpread_resolution_xdpi(BMPHANDLE h)
int bmpread_resolution_ydpi(BMPHANDLE h)
```
#### top-down / bottom-up
`*orientation` is one of:
- `BMP_ORIENT_BOTTOMUP`
- `BMP_ORIENT_TOPDOWN`
`bmpread_orientation()` or the `orientation` value returned by
`bmpread_dimensions()` **is only relevant if you load the BMP file
line-by-line**. In line-by-line mode (using `bmpread_load_line()`), the
image data is always delivered in the order it is in the BMP file. The
`orientation` value will tell you if it's top-down or bottom-up. On the
other hand, when the whole image is loaded at once (using `bmpread_load_image
()`), bmplib will **always** return the image top-down, regardless of how
the BMP file is oriented. The `orientation` value will still indicate the
orientation of the original BMP.
`*orientation` is one of:
- `BMP_ORIENT_BOTTOMUP`
- `BMP_ORIENT_TOPDOWN`
`bmpread_orientation()` or the `orientation` value returned by
`bmpread_dimensions()` **is only relevant if you load the BMP file
line-by-line**. In line-by-line mode (using `bmpread_load_line()`), the
image data is always delivered in the order it is in the BMP file. The
`orientation` value will tell you if it's top-down or bottom-up. On the
other hand, when the whole image is loaded at once (using `bmpread_load_image
()`), bmplib will **always** return the image top-down, regardless of how
the BMP file is oriented. The `orientation` value will still indicate the
orientation of the original BMP.
#### Required size for buffer to receive image
```
size_t bmpread_buffersize(BMPHANDLE h)
```
Returns the buffer size you have to allocate for the whole image.
Returns the buffer size you have to allocate for the whole image.
### Indexed BMPs
@@ -141,7 +143,6 @@ bmpread_load_palette(h, &palette); /* bmplib will allocate the palette
/* or: */
palette = malloc(4 * numcolors);
bmpread_load_palette(h, &palette); /* bmplib will use the provided buffer */
```
Note: Once you have called `bmpread_load_palette()`, both `bmpread_load_image
@@ -175,6 +176,7 @@ void bmpread_set_undefined(BMPHANDLE h, BMPUNDEFINED mode)
```
`mode` can be one of:
- `BMP_UNDEFINED_LEAVE`
- `BMP_UNDEFINED_TO_ALPHA` (default)
@@ -182,7 +184,6 @@ Note: If you use `bmpread_load_palette()` to switch to loading the index data
instead of RGB data, this setting will have no effect and undefined pixels
will always be left alone! (see above)
### Optional settings for 64bit BMPs
```
@@ -207,7 +208,6 @@ Options for `bmpread_set_64bit()` are:
as they are in the BMP file, without any conversion or attempt at
interpretation.
### Setting a number format
By default, bmplib will always return the image data as 8-,16-, or 32-bit integer values. You can instead set the number format to floating point or fixed using:
@@ -218,8 +218,6 @@ BMPRESULT bmp_set_number_format(BMPHANDLE h, BMPFORMAT format)
(see below, *3. General functions for both reading/writing BMPs*)
### Huge files: bmpread_set_insanity_limit()
bmplib will refuse to load images beyond a certain size (default 500MB) and
@@ -232,8 +230,8 @@ void
bmpread_set_insanity_limit(BMPHANDLE h, size_t limit)
```
### Load the image
#### bmpread_load_image()
```
@@ -259,7 +257,6 @@ bmpread_load_image(h, &buffer); /* bmplib will allocate the buffer */
/* or: */
buffer = malloc(bmpread_buffersize(h));
bmpread_load_image(h, &buffer); /* bmplib will use the provided buffer */
```
The image data is written to the buffer according to the returned dimensions
@@ -273,6 +270,7 @@ the file may have been damaged or simply contains invalid image data. Image
data is loaded anyway as far as possible and may be partially usable.
#### bmpread_load_line()
```
BMPRESULT bmpread_load_line(BMPHANDLE h, unsigned char **pbuffer)
```
@@ -303,7 +301,6 @@ returned in whichever order they are stored in the BMP. Use the value
returned by `bmpread_orientation()` to determine if it is top-down or
bottom-up. Almost all BMPs will be bottom-up. (see above)
### Invalid pixels
Invalid pixels may occur in indexed BMPs, both RLE and non-RLE. Invalid pixels
@@ -316,7 +313,6 @@ In both cases, `bmpread_load_image()` and `bmpread_load_line()` will return
BMP_RESULT_INVALID, unless the image is also truncated, then
BMP_RESULT_TRUNCATED is returned.
### Query info about the BMP file
Note: these functions return information about the original BMP file being
@@ -333,7 +329,6 @@ const char* bmpread_info_compression_name(BMPHANDLE h)
BMPRESULT bmpread_info_channel_bits(BMPHANDLE h, int *r, int *g, int *b, int *a)
```
### Release the handle
```
@@ -347,17 +342,16 @@ affected**, so you can call bmp_free() immediately after `bmpread_load_image
Note: Any error message strings returned by `bmp_errmsg()` are invalidated by
`bmp_free()` and must not be used anymore!
## 2. Functions for writing BMP files
### Get a handle
```
BMPHANDLE bmpwrite_new(FILE *file)
```
### Set image dimensions
```
BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
unsigned width,
@@ -366,7 +360,6 @@ BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
unsigned bitsperchannel)
BMPRESULT bmpwrite_set_resolution(BMPHANDLE h, int xdpi, int ydpi)
```
Note: the dimensions set with `bmpwrite_set_dimensions()` describe the source
@@ -374,7 +367,6 @@ data that you pass to bmplib, *not* the output BMP format. Use
`bmpwrite_set_output_bits()`, `bmpwrite_set_palette()`, and
`bmpwrite_set_64bit()` to modify the format written to the BMP file.
### Set the output format
Optional: set the bit-depth for each output channel. bmplib will otherwise
@@ -415,6 +407,7 @@ BMP for 3- or 4-color images, call `bmpwrite_allow_2bit()` before calling
```
BMPRESULT bmpwrite_set_rle(BMPHANDLE h, BMPRLETYPE type)
BMPRESULT bmpwrite_allow_huffman(BMPHANDLE h)
BMPRESULT bmpwrite_set_huffman_img_fg_idx(BMPHANDLE h, int idx)
```
Indexed images may optionally be written run-lenght-encoded (RLE) bitmaps.
@@ -427,16 +420,30 @@ only after explicitly allowing it by calling `bmpwrite_allow_huffman()`
To activate RLE compression, call `bmpwrite_set_rle()` with `type` set to one
of the following values:
- `BMP_RLE_NONE` no RLE compression, same as not calling `bmpwrite_set_rle()`
at all
- `BMP_RLE_AUTO` choose RLE4, RLE8, or 1-D Huffman based on number of colors
in palette
- `BMP_RLE_RLE8` use RLE8, regardless of number of colors in palette
In order to write 1-D Huffman encoded bitmpas, the provided palette must have
2 colors, RLE type must be set to `BMP_RLE_AUTO`, and `bmpwrite_allow_huffman
()` must be called. Be aware that *very* few programs will be able to read
Huffman encoded BMPs!
#### 1-D Huffman encoding
In order to write 1-D Huffman encoded bitmpas,
- the provided palette must have 2 colors,
- RLE type must be set to `BMP_RLE_AUTO`,
- and `bmpwrite_allow_huffman()` must be called.
Be aware that *very* few programs will be able to read Huffman encoded BMPs!
In order to get the best compression result, you should also call
`bmpwrite_set_huffman_img_fg_idx()` to specify which color index (0 or 1) in
the image corresponds to the foreground color. Huffman compression is
optimized for scanned text, meaning short runs of foreground color and long
(er) runs of background color. This will not change the appearance of the
image, but setting it correctly will result in better compression.
#### RLE24
@@ -449,7 +456,6 @@ In order to save an image as RLE24, the data must be provided as 8 bits per
channel RGB (no alpha channel). Call `bmpwrite_set_rle()` with type set to
`BMP_RLE_AUTO` and also call `bmpwrite_allow_rle24()` (in any order).
### top-down / bottom-up
By default, bmplib will write BMP files bottom-up, which is how BMP files are
@@ -463,8 +469,9 @@ BMPRESULT bmpwrite_set_orientation(BMPHANDLE h, BMPORIENT orientation)
```
with `orientation` set to one of the following values:
- `BMPORIENT_BOTTOMUP`
- `BMPORIENT_TOPDOWN`
- `BMPORIENT_BOTTOMUP`
- `BMPORIENT_TOPDOWN`
Note: When writing the whole image at once using `bmpwrite_save_image()`, the
image buffer you provide must **always** be in top-down orientation,
@@ -474,9 +481,6 @@ When writing the image line-by-line using `bmpwrite_save_line()`, you must
provide the image lines in the order according to the orientation you have
chosen for the BMP file.
### 64-bit RGBA BMPs
By default, bmplib will not write 64-bit BMPs because they are rather exotic and hardly any
@@ -492,9 +496,6 @@ In order to make use of the extended range available in 64-bit BMPs (-4.0 to +3.
Note: 64-bit BMPs store pixel values in *linear light*. Unlike when *reading* 64-bit BMPs, bmplib will not make any gamma/linear conversion while writing BMPs. You have to provide the proper linear values in the image buffer.
### Write the image
```
@@ -517,11 +518,6 @@ Important: When writing the whole image at once using `bmpwrite_save_image
line-by-line, the image data must be provided according to the orientation
set with `bmpwrite_set_orientation()` (see above).
## 3. General functions for both reading/writing BMPs
### bmp_free()
@@ -536,7 +532,6 @@ affected, so you can call bmp_free() immediately after bmpread_load_image
by `bmp_errmsg()` are invalidated by `bmp_free()` and cannot be used
anymore.
### bmp_errmsg()
```
@@ -547,7 +542,6 @@ Returns a zero-terminated character string containing the last error
description(s). The returned string is safe to use until any other
bmplib-function is called with the same handle.
### bmp_set_number_format()
```
@@ -562,7 +556,6 @@ sets the number format of the image buffer received from / passed to bmplib. `fo
For indexed images, `BMP_FORMAT_INT` is the only valid format.
### bmp_version()
```
@@ -571,9 +564,6 @@ const char* bmp_version(void)
Returns a zero-terminated character string containing the version of bmplib.
## 4. Data types and constants
#### `BMPHANDLE`
@@ -608,10 +598,10 @@ else {
}
```
#### `BMPINFOVER`
Returned by `bmpread_info_header_version()`. Possible values are:
- `BMPINFO_CORE_OS21` BITMAPCOREHEADER aka OS21XBITMAPHEADER (12 bytes)
- `BMPINFO_OS22` OS22XBITMAPHEADER (16/40/64 bytes)
- `BMPINFO_V3` BITMAPINFOHEADER (40 bytes)
@@ -627,6 +617,7 @@ from `BMPINFO_CORE_OS21` to `BMPINFO_FUTURE`.
#### `BMPRLETYPE`
Used in `bmpwrite_set_rle()`. Possible values are:
- `BMP_RLE_NONE` No RLE
- `BMP_RLE_AUTO` RLE4 or RLE8, chosen based on number of colors in palette
- `BMP_RLE_RLE8` Use RLE8 for any number of colors in palette
@@ -636,6 +627,7 @@ Can safely be cast from/to int.
#### `BMPUNDEFINED`
Used in `bmpread_set_undefined()`. Possible values are:
- `BMP_UNDEFINED_TO_ALPHA` (default)
- `BMP_UNDEFINED_TO_ZERO`
@@ -644,6 +636,7 @@ Can safely be cast from/to int.
#### `BMPCONV64`
Used in `bmpread_set_64bit_conv()`. Possible values are:
- `BMP_CONV64_SRGB` (default)
- `BMP_CONV64_LINEAR`
- `BMP_CONV64_NONE`
@@ -653,12 +646,11 @@ Can safely be cast from/to int.
#### `BMPFORMAT`
Used in `bmp_set_number_format()`. Possible values are:
- `BMP_FORMAT_INT` (default)
- `BMP_FORMAT_FLOAT` 32-bit floating point
- `BMP_FORMAT_S2_13` s2.13 fixed point
## 5. Sample code
### Reading BMPs
@@ -713,7 +705,6 @@ Used in `bmp_set_number_format()`. Possible values are:
*/
```
### Writing BMPs
```

View File

@@ -175,6 +175,7 @@ struct Bmpwrite {
size_t bytes_written_before_bitdata;
bool has_alpha;
enum BmpOrient outorientation;
bool huffman_fg_idx;
struct Colormask cmask;
enum BmpRLEtype rle_requested;
int rle; /* 1, 4, or 8 */
@@ -243,72 +244,67 @@ int16_t s16_from_le(const unsigned char *buf);
#define BMPFILE_PT 0x5450
#define BMPFHSIZE 14
#define BMPIHSIZE_V3 40
#define BMPIHSIZE_V4 108
#define BMPIHSIZE_OS22 64
typedef uint16_t WORD;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint8_t BYTE;
#define BMPFHSIZE 14
#define BMPIHSIZE_V3 40
#define BMPIHSIZE_V4 108
#define BMPIHSIZE_OS22 64
struct Bmpfile {
WORD type; /* "BM" */
DWORD size; /* bytes in file */
WORD reserved1;
WORD reserved2;
DWORD offbits;
uint16_t type; /* "BM" */
uint32_t size; /* bytes in file */
uint16_t reserved1;
uint16_t reserved2;
uint32_t offbits;
};
struct Bmpinfo {
/* BITMAPINFOHEADER (40 bytes) */
DWORD size; /* sizof struct */
LONG width;
LONG height;
WORD planes;
WORD bitcount;
DWORD compression;
DWORD sizeimage; /* 0 ok for uncompressed */
LONG xpelspermeter;
LONG ypelspermeter;
DWORD clrused;
DWORD clrimportant;
uint32_t size; /* sizof struct */
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bitcount;
uint32_t compression;
uint32_t sizeimage; /* 0 ok for uncompressed */
int32_t xpelspermeter;
int32_t ypelspermeter;
uint32_t clrused;
uint32_t clrimportant;
/* BITMAPV4INFOHEADER (108 bytes) */
DWORD redmask;
DWORD greenmask;
DWORD bluemask;
DWORD alphamask;
DWORD cstype;
LONG redX;
LONG redY;
LONG redZ;
LONG greenX;
LONG greenY;
LONG greenZ;
LONG blueX;
LONG blueY;
LONG blueZ;
DWORD gammared;
DWORD gammagreen;
DWORD gammablue;
uint32_t redmask;
uint32_t greenmask;
uint32_t bluemask;
uint32_t alphamask;
uint32_t cstype;
int32_t redX;
int32_t redY;
int32_t redZ;
int32_t greenX;
int32_t greenY;
int32_t greenZ;
int32_t blueX;
int32_t blueY;
int32_t blueZ;
uint32_t gammared;
uint32_t gammagreen;
uint32_t gammablue;
/* BITMAPV5INFOHEADER (124 bytes) */
DWORD intent;
DWORD profiledata;
DWORD profilesize;
DWORD reserved;
uint32_t intent;
uint32_t profiledata;
uint32_t profilesize;
uint32_t reserved;
/* OS22XBITMAPHEADER */
WORD resolution; /* = 0 */
WORD orientation; /* = 0 */
WORD halftone_alg;
DWORD halftone_parm1;
DWORD halftone_parm2;
DWORD color_encoding; /* = 0 (RGB) */
DWORD app_id;
uint16_t resolution; /* = 0 */
uint16_t orientation; /* = 0 */
uint16_t halftone_alg;
uint32_t halftone_parm1;
uint32_t halftone_parm2;
uint32_t color_encoding; /* = 0 (RGB) */
uint32_t app_id;
/* internal only, not from file: */
enum BmpInfoVer version;
/* internal only, not from file: */
enum BmpInfoVer version;
};

View File

@@ -23,6 +23,7 @@
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#define BMPLIB_LIB
@@ -33,7 +34,6 @@
#include "bmp-common.h"
#include "huffman.h"
#include "bmp-read.h"
#include "reversebits.h"
/*
@@ -499,29 +499,58 @@ static inline bool s_read_rgb_pixel(BMPREAD_R rp, union Pixel *restrict px)
* s_read_indexed_line
* - 1/2/4/8 bits non-RLE indexed
*******************************************************/
static inline bool s_read_n_bytes(BMPREAD_R rp, int n, unsigned long *restrict buff);
static inline unsigned long s_bits_from_buffer(unsigned long buf, int size,
int nbits, int used_bits);
struct Buffer32 {
uint32_t buffer;
int n;
};
static inline bool s_buffer32_fill(BMPREAD_R rp, struct Buffer32 *restrict buf)
{
int byte;
memset(buf, 0, sizeof *buf);
for (int i = 0; i < 4; i++) {
if (EOF == (byte = s_read_one_byte(rp))) {
s_set_file_error(rp);
return false;
}
buf->buffer <<= 8;
buf->buffer |= byte;
}
buf->n = 32;
return true;
}
static inline uint32_t s_buffer32_bits(struct Buffer32 *restrict buf, int nbits)
{
uint32_t result;
assert(nbits < 32);
result = buf->buffer >> (32 - nbits);
buf->buffer = (buf->buffer << nbits) & 0xffffffffUL;
buf->n -= nbits;
return result;
}
static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line)
{
int bits_used, buffer_size, x = 0, v;
bool done = false;
unsigned long buffer;
size_t offs;
int x = 0, v;
bool done = false;
struct Buffer32 buffer;
size_t offs;
/* setting the buffer size to the alignment takes care of padding bytes */
buffer_size = 32;
/* the buffer size of 32 bits takes care of padding bytes */
while (!done && s_read_n_bytes(rp, buffer_size / 8, &buffer)) {
while (!done && s_buffer32_fill(rp, &buffer)) {
bits_used = 0;
while (bits_used < buffer_size) {
while (buffer.n >= rp->ih->bitcount) {
/* mask out the relevant bits for current pixel */
v = (int) s_bits_from_buffer(buffer, buffer_size,
rp->ih->bitcount, bits_used);
bits_used += rp->ih->bitcount;
v = (int) s_buffer32_bits(&buffer, rp->ih->bitcount);
if (v >= rp->palette->numcolors) {
v = rp->palette->numcolors - 1;
@@ -537,6 +566,7 @@ static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line)
line[offs+2] = rp->palette->color[v].blue;
s_int_to_result_format(rp, 8, line + offs);
}
if (++x == rp->width) {
done = true;
break; /* discarding rest of buffer == padding */
@@ -547,51 +577,6 @@ static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line)
/********************************************************
* s_read_n_bytes
*******************************************************/
static inline bool s_read_n_bytes(BMPREAD_R rp, int n, unsigned long *restrict buff)
{
int byte;
*buff = 0;
while (n--) {
if (EOF == (byte = s_read_one_byte(rp))) {
s_set_file_error(rp);
return false;
}
*buff <<= 8;
*buff |= byte;
}
return true;
}
/********************************************************
* s_bits_from_buffer
*******************************************************/
static inline unsigned long s_bits_from_buffer(unsigned long buf, int size,
int nbits, int used_bits)
{
unsigned long mask;
int shift;
shift = size - (nbits + used_bits);
mask = (1U << nbits) - 1;
mask <<= shift;
buf &= mask;
buf >>= shift;
return buf;
}
/********************************************************
* s_read_rle_line
* - 4/8/24 bit RLE
@@ -787,7 +772,7 @@ static void s_read_huffman_line(BMPREAD_R rp, unsigned char *restrict line)
if (rp->hufbuf_len == 0)
break;
if ((rp->hufbuf & 0x00ff) == 0) {
if ((rp->hufbuf & 0xff000000UL) == 0) {
if (!s_huff_skip_eol(rp)) {
rp->truncated = true;
break;
@@ -837,11 +822,11 @@ static bool s_huff_skip_eol(BMPREAD_R rp)
huff_fillbuf(rp);
continue;
}
while ((rp->hufbuf & 0x0001) == 0) {
rp->hufbuf >>= 1;
while ((rp->hufbuf & 0x80000000UL) == 0) {
rp->hufbuf <<= 1;
rp->hufbuf_len--;
}
rp->hufbuf >>= 1;
rp->hufbuf <<= 1;
rp->hufbuf_len--;
return true;
}
@@ -858,12 +843,12 @@ static bool s_huff_find_eol(BMPREAD_R rp)
huff_fillbuf (rp);
while (rp->hufbuf_len > 11)
{
if ((rp->hufbuf & 0x07ff) == 0) {
rp->hufbuf >>= 11;
if ((rp->hufbuf & 0xffe00000UL) == 0) {
rp->hufbuf <<= 11;
rp->hufbuf_len -= 11;
return s_huff_skip_eol (rp);
}
rp->hufbuf >>= 1;
rp->hufbuf <<= 1;
rp->hufbuf_len -= 1;
if (rp->hufbuf_len < 12)
huff_fillbuf (rp);

View File

@@ -95,7 +95,6 @@ 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);
static bool s_check_dimensions(BMPREAD_R rp);
API BMPRESULT bmpread_load_info(BMPHANDLE h)
{
@@ -107,7 +106,6 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
if (rp->getinfo_called)
return rp->getinfo_return;
if (!s_read_file_header(rp))
goto abort;
@@ -135,14 +133,16 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
goto abort;
rp->width = (int) rp->ih->width;
rp->height = (unsigned) rp->ih->height;
/* negative height flips the image vertically */
if (rp->ih->height < 0) {
rp->orientation = BMP_ORIENT_TOPDOWN;
rp->height = - (int64_t) rp->ih->height;
rp->height = (unsigned) (-(int64_t)rp->ih->height);
} else {
rp->height = (unsigned) rp->ih->height;
}
if (rp->ih->compression == BI_RLE4 ||
rp->ih->compression == BI_RLE8 ||
rp->ih->compression == BI_OS2_RLE24)
@@ -176,7 +176,6 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
if (!(rp->palette = s_read_palette(rp)))
goto abort;
} else if (!rp->rle) { /* RGB */
memset(&rp->cmask, 0, sizeof rp->cmask);
if (!s_read_colormasks(rp))
goto abort;
@@ -192,9 +191,6 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
if (!br_set_resultbits(rp))
goto abort;
if (!s_check_dimensions(rp))
goto abort;
if (rp->insanity_limit &&
rp->result_size > rp->insanity_limit) {
logerr(rp->log, "file is insanely large");
@@ -704,30 +700,6 @@ static bool s_is_bmptype_supported_indexed(BMPREAD_R rp)
/*****************************************************************************
* 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 ||
rp->height > INT32_MAX) {
logerr(rp->log, "Invalid BMP dimensions (%dx%d)",
(int) rp->ih->width, (int) rp->ih->height);
rp->lasterr = BMP_ERR_DIMENSIONS;
return false;
}
return true;
}
/*****************************************************************************
* s_read_palette
*****************************************************************************/
@@ -826,6 +798,107 @@ static struct Palette* s_read_palette(BMPREAD_R rp)
/*****************************************************************************
* 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->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->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->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->dimensions_queried = false;
}
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->getinfo_called) {
if (rp->insanity_limit && rp->result_size > rp->insanity_limit) {
if (rp->getinfo_return == BMP_RESULT_OK) {
logerr(rp->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 || rp->height > INT32_MAX) {
logerr(rp->log, "Invalid BMP dimensions (%dx%u)", rp->width, rp->height);
rp->lasterr = BMP_ERR_DIMENSIONS;
return false;
}
return true;
}
/*****************************************************************************
* s_read_colormasks
*****************************************************************************/
@@ -892,80 +965,6 @@ static bool s_read_colormasks(BMPREAD_R rp)
/*****************************************************************************
* br_set_resultbits
*****************************************************************************/
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->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->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->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->dimensions_queried = false;
}
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;
rp->result_size = (size_t) rp->width * rp->height * rp->result_bytes_per_pixel;
if (rp->getinfo_called) {
if (rp->insanity_limit && rp->result_size > rp->insanity_limit) {
if (rp->getinfo_return == BMP_RESULT_OK) {
logerr(rp->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_read_masks_from_bitfields
*****************************************************************************/

View File

@@ -67,6 +67,7 @@ API BMPHANDLE bmpwrite_new(FILE *file)
wp->rle_requested = BMP_RLE_NONE;
wp->outorientation = BMP_ORIENT_BOTTOMUP;
wp->source_format = BMP_FORMAT_INT;
wp->huffman_fg_idx = 1;
if (!(wp->log = logcreate()))
goto abort;
@@ -346,8 +347,8 @@ API BMPRESULT bmpwrite_set_resolution(BMPHANDLE h, int xdpi, int ydpi)
if (s_check_already_saved(wp))
return BMP_RESULT_ERROR;
wp->ih->xpelspermeter = (LONG) (100.0 / 2.54 * xdpi + 0.5);
wp->ih->ypelspermeter = (LONG) (100.0 / 2.54 * ydpi + 0.5);
wp->ih->xpelspermeter = (int32_t) (100.0 / 2.54 * xdpi + 0.5);
wp->ih->ypelspermeter = (int32_t) (100.0 / 2.54 * ydpi + 0.5);
return BMP_RESULT_OK;
}
@@ -439,6 +440,25 @@ API BMPRESULT bmpwrite_set_64bit(BMPHANDLE h)
}
/*****************************************************************************
* bmpwrite_set_huffman_img_fg_idx
*****************************************************************************/
API BMPRESULT bmpwrite_set_huffman_img_fg_idx(BMPHANDLE h, int idx)
{
BMPWRITE wp;
if (!(wp = cm_write_handle(h)))
return BMP_RESULT_ERROR;
if (s_check_already_saved(wp))
return BMP_RESULT_ERROR;
wp->huffman_fg_idx = !!idx;
return BMP_RESULT_OK;
}
/*****************************************************************************
* s_check_already_saved
@@ -690,10 +710,10 @@ static void s_decide_outformat(BMPWRITE_R wp)
wp->outbytes_per_pixel = wp->ih->bitcount / 8;
if (wp->ih->version >= BMPINFO_V4 && !wp->out64bit) {
wp->ih->redmask = (DWORD) (wp->cmask.mask.red << wp->cmask.shift.red);
wp->ih->greenmask = (DWORD) (wp->cmask.mask.green << wp->cmask.shift.green);
wp->ih->bluemask = (DWORD) (wp->cmask.mask.blue << wp->cmask.shift.blue);
wp->ih->alphamask = (DWORD) (wp->cmask.mask.alpha << wp->cmask.shift.alpha);
wp->ih->redmask = (uint32_t) (wp->cmask.mask.red << wp->cmask.shift.red);
wp->ih->greenmask = (uint32_t) (wp->cmask.mask.green << wp->cmask.shift.green);
wp->ih->bluemask = (uint32_t) (wp->cmask.mask.blue << wp->cmask.shift.blue);
wp->ih->alphamask = (uint32_t) (wp->cmask.mask.alpha << wp->cmask.shift.alpha);
}
}
@@ -703,7 +723,7 @@ static void s_decide_outformat(BMPWRITE_R wp)
filesize = bitmapsize + BMPFHSIZE + wp->ih->size + wp->palette_size;
wp->fh->type = 0x4d42; /* "BM" */
wp->fh->size = (DWORD) ((wp->rle || filesize > UINT32_MAX) ? 0 : filesize);
wp->fh->size = (uint32_t) ((wp->rle || filesize > UINT32_MAX) ? 0 : filesize);
wp->fh->offbits = BMPFHSIZE + wp->ih->size + wp->palette_size;
wp->ih->width = wp->width;
@@ -712,7 +732,7 @@ static void s_decide_outformat(BMPWRITE_R wp)
else
wp->ih->height = -wp->height;
wp->ih->planes = 1;
wp->ih->sizeimage = (DWORD) ((wp->rle || bitmapsize > UINT32_MAX) ? 0 : bitmapsize);
wp->ih->sizeimage = (uint32_t) ((wp->rle || bitmapsize > UINT32_MAX) ? 0 : bitmapsize);
}
@@ -908,7 +928,7 @@ static bool s_save_header(BMPWRITE_R wp)
* We ignore any errors quietly, as there's nothing we
* can do and most (all?) readers ignore those sizes in
* the header, anyway. Same goes for file/bitmap sizes
* which are too big for the respective fields.
* when they are too big for the respective fields.
*****************************************************************************/
static bool s_try_saving_image_size(BMPWRITE_R wp)
@@ -1023,7 +1043,7 @@ static bool s_save_line_rle(BMPWRITE_R wp, const unsigned char *line)
{
int i, j, k, x, l, dx, outbyte = 0;
bool even;
int small_number, minlen = 0;
int small_number = 0, minlen = 0;
switch (wp->rle) {
case 4:
@@ -1220,7 +1240,7 @@ static bool s_save_line_huff(BMPWRITE_R wp, const unsigned char *line)
while (x < wp->width) {
len = 0;
while ((len < wp->width - x) && ((!!line[x + len]) == black))
while ((len < wp->width - x) && ((!!line[x + len]) == (black ^ !wp->huffman_fg_idx)))
len++;
if (!huff_encode(wp, len, black))
goto abort;
@@ -1395,9 +1415,9 @@ static inline uint16_t float_to_s2_13(double d)
d = round(d * 8192.0);
if (d >= 32768.0)
if (d >= 32767.0)
s2_13 = 0x7fff; /* max positive value */
else if (d < -32768.0)
else if (d <= -32768.0)
s2_13 = 0x8000; /* min negative value */
else
s2_13 = (uint16_t) (0xffff & (int)d);
@@ -1413,11 +1433,16 @@ static inline uint16_t float_to_s2_13(double d)
static bool s_write_palette(BMPWRITE_R wp)
{
int i, c;
int i, c;
bool reverse = false;
if (wp->rle == 1)
reverse = !wp->huffman_fg_idx;
for (i = 0; i < wp->palette->numcolors; i++) {
int idx = reverse ? wp->palette->numcolors - i - 1 : i;
for (c = 0; c < 3; c++) {
if (EOF == s_write_one_byte(wp->palette->color[i].value[2-c], wp))
if (EOF == s_write_one_byte(wp->palette->color[idx].value[2-c], wp))
return false;
}
if (EOF == s_write_one_byte(0, wp))
@@ -1493,7 +1518,7 @@ static bool s_write_bmp_info_header(BMPWRITE_R wp)
return false;
}
#endif
for (int i = 0; (DWORD) i < wp->ih->size - 40; i++) {
for (int i = 0; (uint32_t) i < wp->ih->size - 40; i++) {
if (EOF == putc(0, wp->file))
return false;
wp->bytes_written++;

View File

@@ -124,12 +124,10 @@ typedef enum Bmpconv64 BMPCONV64;
/*
* BMP info header versions
*
* There doesn't seem to be consensus on whether the
* BITMAPINFOHEADER is version 1 (with the two Adobe
* extensions being v2 and v3) or version 3 (with the
* older BITMAPCIREHEADER and OS22XBITMAPHEADER being
* v1 and v2).
* I am going with BITMAPINFOHEADER = v3
* There doesn't seem to be consensus on whether the BITMAPINFOHEADER is
* version 1 (with the two Adobe extensions being v2 and v3) or version 3
* (with the older BITMAPCOREHEADER and OS22XBITMAPHEADER being v1 and v2).
* I am going with BITMAPINFOHEADER = v3.
*/
enum BmpInfoVer {
BMPINFO_CORE_OS21 = 1, /* 12 bytes */
@@ -268,6 +266,7 @@ APIDECL BMPRESULT bmpwrite_allow_rle24(BMPHANDLE h);
APIDECL BMPRESULT bmpwrite_set_rle(BMPHANDLE h, BMPRLETYPE type);
APIDECL BMPRESULT bmpwrite_set_orientation(BMPHANDLE h, BMPORIENT orientation);
APIDECL BMPRESULT bmpwrite_set_64bit(BMPHANDLE h);
APIDECL BMPRESULT bmpwrite_set_huffman_img_fg_idx(BMPHANDLE h, int idx);
APIDECL BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image);
APIDECL BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line);

View File

@@ -1,82 +0,0 @@
/* bmplib - gen-reversebits.c
*
* Copyright (c) 2024, 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 <stdio.h>
static int reverse(int val, int bits)
{
int mask;
bits /= 2;
if (bits == 0)
return val;
mask = (1 << bits) - 1;
return (reverse(val & mask, bits) << bits) | reverse(val >> bits, bits);
}
int main(int argc, char *argv[])
{
int reversed, i;
FILE *file;
const char *src_name = "reversebits.h";
const char *this_name = "gen-reversebits.c";
if (argc == 2) {
if (!(file = fopen(argv[1], "w"))) {
perror(argv[1]);
return 1;
}
} else {
file = stdout;
}
fprintf(file, "/* bmplib - %s\n", src_name);
fprintf(file, " *\n"
" * Copyright (c) 2024, Rupert Weber.\n"
" *\n"
" * This file is part of bmplib.\n"
" * bmplib is free software: you can redistribute it and/or modify\n"
" * it under the terms of the GNU Lesser General Public License as\n"
" * published by the Free Software Foundation, either version 3 of\n"
" * the License, or (at your option) any later version.\n"
" *\n");
fprintf(file, " * This program is distributed in the hope that it will be useful,\n"
" * but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
" * GNU Lesser General Public License for more details.\n"
" *\n"
" * You should have received a copy of the GNU Lesser General Public\n"
" * License along with this library.\n"
" * If not, see <https://www.gnu.org/licenses/>\n"
" */\n\n");
fprintf(file, "/* This file is auto-generated by %s */\n\n\n", this_name);
fprintf(file, "static const unsigned char reversebits[] = {\n\t");
for (i = 0; i < 256; i++) {
reversed = reverse(i, 8);
fprintf(file, "0x%02x, ", reversed);
if ((i + 1) % 8 == 0 && i < 255)
fprintf(file, "\n\t");
}
fprintf(file, "\n};\n");
return 0;
}

View File

@@ -29,7 +29,6 @@
#include "bmplib.h"
#include "logging.h"
#include "bmp-common.h"
#include "reversebits.h"
#include "huffman.h"
#include "huffman-codes.h"
@@ -63,7 +62,7 @@ int huff_decode(BMPREAD_R rp, int black)
}
result += nodebuffer[idx].value;
rp->hufbuf >>= bits_used;
rp->hufbuf <<= bits_used;
rp->hufbuf_len -= bits_used;
} while (nodebuffer[idx].makeup && result < INT_MAX - 2560);
@@ -85,12 +84,12 @@ static int s_findnode(uint32_t bits, int nbits, bool black, int *found)
idx = black ? blackroot : whiteroot;
while (idx != -1 && !nodebuffer[idx].terminal && bits_used < nbits) {
if (bits & 1)
if (bits & 0x80000000UL)
idx = nodebuffer[idx].r;
else
idx = nodebuffer[idx].l;
bits_used++;
bits >>= 1;
bits <<= 1;
}
*found = idx;
return idx != -1 ? bits_used : 0;
@@ -110,8 +109,7 @@ void huff_fillbuf(BMPREAD_R rp)
if (EOF == (byte = getc(rp->file)))
break;
rp->bytes_read++;
byte = reversebits[byte];
rp->hufbuf |= ((uint32_t)byte) << rp->hufbuf_len;
rp->hufbuf |= (uint32_t)byte << (24 - rp->hufbuf_len);
rp->hufbuf_len += 8;
}
}

View File

@@ -1,4 +1,4 @@
project('bmplib', 'c', default_options: ['c_std=c11'], version: '1.7.3')
project('bmplib', 'c', default_options: ['c_std=c11'], version: '1.7.4')
cc = meson.get_compiler('c')
@@ -7,6 +7,8 @@ add_project_arguments('-pedantic', language : 'c')
add_project_arguments('-fvisibility=hidden', language: 'c')
if get_option('buildtype') == 'debug'
add_project_arguments('-DDEBUG', language: 'c')
elif get_option('buildtype') == 'release'
add_project_arguments('-DNDEBUG', language: 'c')
endif
m_dep = cc.find_library('m', required : false)
@@ -37,22 +39,15 @@ elif cc.sizeof('int') < 4
error('sizeof(int) must be at least 32 bit.')
endif
gen_huffman = executable('gen-huffman', 'gen-huffman.c')
huff_codes = custom_target('huffman-codes.h',
output: 'huffman-codes.h',
command: [gen_huffman, '@OUTPUT@'],
)
gen_reversebits = executable('gen-reversebits', 'gen-reversebits.c')
reversebits = custom_target('reversebits.h',
output: 'reversebits.h',
command: [gen_reversebits, '@OUTPUT@'],
)
bmplib = shared_library('bmp',
[bmplib_sources, huff_codes[0], reversebits[0]],
[bmplib_sources, huff_codes[0]],
version: meson.project_version(),
install: true,
dependencies: m_dep,
@@ -65,4 +60,3 @@ pkg_mod.generate(libraries: bmplib,
filebase: 'libbmp',
description: 'Library for reading/writing Windows BMP files.',
)