54 Commits
v1.7.6 ... main

Author SHA1 Message Date
Rupert
e7910ac36b add bmpread_image_type() API call 2025-06-22 02:05:35 +02:00
Rupert
7ece494541 Docs update 2025-06-21 16:18:45 +02:00
Rupert
c8ead48e33 add icon/pointer settings-sanity check 2025-06-21 13:24:54 +02:00
Rupert
8b38742ea3 icons/pointers: allow only BMP_UNDEFINED_LEAVE as undefined mode 2025-06-21 09:32:50 +02:00
Rupert
3a673da403 icons/pointers/arrays: minor changes to error handling 2025-06-21 09:32:19 +02:00
Rupert
fabbe4f8af arrays: bmpread_load_info() didn't set rp->getinfo_return
also icons/pointers: minor changes, comments
2025-06-06 16:24:14 +02:00
Rupert
22ef9d73aa Add BA and icon/pointer documentation 2025-06-04 00:03:26 +02:00
Rupert
abd3ea2db2 remove long deprecated symbols, bump version to 1.8.0 2025-06-03 23:16:10 +02:00
Rupert
3c5723b740 OS/2 icons/pointers: require INT result format 2025-06-03 23:21:32 +02:00
Rupert
8dd7182bf1 refuse 64bit for icons/pointers 2025-06-03 23:21:32 +02:00
Rupert
07d3bf6086 OS/2 icons/pointers: refactor boolean flags
one icon flag and and mono flag instead of two color-icon/mono-icon
flags
2025-06-03 23:21:32 +02:00
Rupert
5cd33b22ca Add support for OS/2 BA bitmap arrays 2025-06-03 23:21:32 +02:00
Rupert
10657851d1 Add support for OS/2 icons and pointers (IC/PT/CI/CP)
Still somewhat crude, but it works.

Limitations:
- max size 512x512
- no RLE
- no support yet to load index values, result is
  always returned as RGBA
2025-06-03 23:21:32 +02:00
Rupert
044fa148ed bmp-write: remove duplicate code 2025-05-30 22:22:24 +02:00
Rupert
b9244c0766 embrace C11 VLA syntax 2025-05-30 17:01:10 +02:00
Rupert
84a0f0f49d struct Bmpread - reorder some fields wrt alignment 2025-05-30 16:50:16 +02:00
Rupert
1b67440d29 docs: add section numbering 2025-05-27 15:08:47 +02:00
Rupert
5166bdb1e7 bmplib.h: some houskeeping and preparation for OS/2 icon/pointers 2025-05-14 23:36:08 +02:00
Rupert
c02804cdbf asserts
- use static_assert macro instead of _Static_assert
- make assertion in bmpwrite_set_iccprofile static
2025-05-09 08:12:07 +02:00
Rupert
fd8583d568 unit tests: add libreoffice spreadsheet to help construct test data 2025-05-08 15:14:32 +02:00
Rupert
845223bf62 add unit test for s_imgrgb_to_outbytes() 2025-05-08 13:20:47 +02:00
Rupert
952c10324f bmp-write: update float_to_s2_13() to version from bmp-read
should obviously eventually go into a header file instead of
duplicating it.
2025-05-07 20:56:56 +02:00
Rupert
668cc352e3 add unit tests for write_<xnn>_le() functions 2025-05-07 17:55:07 +02:00
Rupert
1bba98e395 refactor cm_align4size() macro 2025-05-03 18:41:08 +02:00
Rupert
0815eb3a7b remove unused function cm_align2padding() 2025-05-03 14:55:40 +02:00
Rupert
94dceb5812 read_u<nn>_le(): eliminate implementation defined behavior
relatively pointless but fun ;o)

- while not making any assumptions about the host's bit representation
  or the implementation defined behavior of assigning an unsigned int
  > INT_MAX to a signed int, the lack of range (INT_MIN) would
  still break it on non-two's complement unless larger int types were
  used.
- both gcc and clang recognize the pattern and generate the same code as
  for the simple assignment, so doing it this way doesn't cost any
  performance.
2025-05-03 13:25:21 +02:00
Rupert
c733595aed add unit test for read_s32_le() 2025-05-03 11:40:31 +02:00
Rupert
920ba8ea7e add unit test for read_u32_le() 2025-05-03 11:32:51 +02:00
Rupert
0882f1796a add unit test for read_s16_le() 2025-05-03 11:32:51 +02:00
Rupert
da95bcb0ae add unit test for read_u16_le() 2025-05-03 11:32:51 +02:00
Rupert
e5584c3b0f s_buffer32_bits(): make assert more precise 2025-05-01 12:13:32 +02:00
Rupert
3af484b238 update copyright notice dates 2025-05-01 12:11:32 +02:00
Rupert
6c33d975b5 add unit test for s_read_rgb_pixel() 2025-05-01 12:04:27 +02:00
Rupert
0ab592c357 add unit test for s_buffer_32_bits() 2025-04-30 13:23:55 +02:00
Rupert
e9b047b857 add unit test for s_buffer32_fill() 2025-04-30 13:23:55 +02:00
Rupert
71b3853327 s_buffer32_fill(): allow partial fill
this can make sense if the very last (padding) bytes of an indexed
file are truncated.
2025-04-30 13:23:55 +02:00
Rupert
5ce902ce1b add unit test for s_int8_to_result_format() 2025-04-30 13:23:55 +02:00
Rupert
abf93c1a77 reafactor s_int_to_result_format()
the in-place conversion made me feel a bit queezy; also, it's only ever
called for rgb conversion of indexed images, meaning always with
8-bit integers.

- Changed the name to s_int8_to_result_format()
- made it 8-bit only
- source values are now supplied as separate int array 'frombits' and
  stored into the result buffer 'px'.
2025-04-30 13:23:55 +02:00
Rupert
bc7dd1423c add unit test for s_srgb_gamma_float(), rename test source file 2025-04-29 00:59:13 +02:00
Rupert
100715fe9d add unit tests for s2.13 conversion 2025-04-28 22:59:12 +02:00
Rupert
e195d1d918 bmpread: use conversion functions
instead of manual conversions,
- use new s_float_to_s2_13()
- use s_int_to_float() in s_int_to_result_format()
2025-04-28 22:59:12 +02:00
Rupert
3989e07e05 fix rounding error in s2.13 conversion, eliminate impl.defined behavior
also eliminate some duplicate code while we're at it
2025-04-28 22:56:34 +02:00
Rupert
df8d9e44ca read state, minor changes 2025-04-28 22:56:34 +02:00
Rupert
7d2ff7fca5 C23 doesn't define __bool_true_false_are_defined anymore
this is still very kludgy, but seems to work for any c std
2025-04-28 22:56:34 +02:00
Rupert
2ba0910acc static assert of 32bit requirement
be more specific, requiring 32bits, not necessarily 4 bytes
2025-04-28 19:41:02 +02:00
Rupert
7494855af6 cleanup insane handling
- fix: setting a new insanity limit can cause previously ok result to
  be insane
- refactor for simpler expressions / more symmetry
2025-04-19 19:23:46 +02:00
Rupert
1d9a3faad4 icc profile error handling, minor changes 2025-04-19 12:53:52 +02:00
Rupert
3d31aa93cd minor fixes:
- require integer format when loading palette
- don't auto-load info in bmpread_is_64bit, as we cannot return result
2025-04-19 01:30:41 +02:00
Rupert
91d93cba9b refactor read state
instead of several bools, have one enum to keep track of where in the
reading process we are
2025-04-19 01:27:22 +02:00
Rupert
b43fd940e6 bump version to 1.7.7 2025-04-18 15:53:08 +02:00
Rupert
61a4caf6ef fix line-by-line writing bug, refactor write state
bug introduced by 142427e17: line-by-line writing of RLE files resulted
in corrupted files.
Found while refactoring write state
2025-04-18 15:51:09 +02:00
Rupert
4942795e14 don't require explicitly calling load_info() before reading ICC profile 2025-04-17 23:48:19 +02:00
Rupert
52801cd5d5 cleanup s_decide_outformat() 2025-04-17 23:47:24 +02:00
Rupert
0e4e611955 add semicolons in doc for consistent syntax highlighter 2025-04-16 23:50:00 +02:00
27 changed files with 2646 additions and 576 deletions

View File

@@ -1,10 +1,10 @@
# Rupert's bmplib -- Full API Description (v1.7.6)
# Rupert's bmplib -- Full API Description (v1.8.0)
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
### 1.2 Get a handle
```c
BMPHANDLE bmpread_new(FILE *file)
@@ -16,7 +16,7 @@ handle.
The handle cannot be reused to read multiple files.
### Read the file header
### 1.3 Read the file header
```c
BMPRESULT bmpread_load_info(BMPHANDLE h)
@@ -42,7 +42,7 @@ 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
### 1.4 Get image dimensions
```c
BMPRESULT bmpread_dimensions(BMPHANDLE h,
@@ -50,7 +50,7 @@ BMPRESULT bmpread_dimensions(BMPHANDLE h,
int *height,
int *channels,
int *bitsperchannel,
BMPORIENT *orientation)
BMPORIENT *orientation);
```
Use `bmpread_dimensions()` to get all dimensions with one call. It is not
@@ -69,17 +69,17 @@ Note, in order to use these functions, -- unlike with `bmpread_dimensions
they will all return 0!
```c
int bmpread_width(BMPHANDLE h)
int bmpread_height(BMPHANDLE h)
int bmpread_channels(BMPHANDLE h)
int bmpread_bitsperchannel(BMPHANDLE h)
BMPORIENT bmpread_orientation(BMPHANDLE h)
int bmpread_width(BMPHANDLE h);
int bmpread_height(BMPHANDLE h);
int bmpread_channels(BMPHANDLE h);
int bmpread_bitsperchannel(BMPHANDLE h);
BMPORIENT bmpread_orientation(BMPHANDLE h);
int bmpread_resolution_xdpi(BMPHANDLE h)
int bmpread_resolution_ydpi(BMPHANDLE h)
int bmpread_resolution_xdpi(BMPHANDLE h);
int bmpread_resolution_ydpi(BMPHANDLE h);
```
#### top-down / bottom-up
#### 1.4.1. top-down / bottom-up
`*orientation` is one of:
@@ -96,15 +96,38 @@ other hand, when the whole image is loaded at once (using `bmpread_load_image
the BMP file is oriented. The `orientation` value will still indicate the
orientation of the original BMP.
#### Required size for buffer to receive image
#### 1.4.2. Required size for buffer to receive image
```c
size_t bmpread_buffersize(BMPHANDLE h)
size_t bmpread_buffersize(BMPHANDLE h);
```
Returns the buffer size you have to allocate for the whole image.
### Indexed BMPs
### 1.5. Image type
```c
BMPIMAGETYPE bmpread_image_type(BMPHANDLE h);
```
Returns one of
- `BMP_IMAGETYPE_NONE` type hasn't been determined (yet). Either
`bmpread_load_info()` hasn't been called yet, or the file is not a valid
BMP file.
- `BMP_IMAGETYPE_BM` bitmap (normal image)
- `BMP_IMAGETYPE_BA` bitmap array
- `BMP_IMAGETYPE_CI` color icon
- `BMP_IMAGETYPE_CP` color pointer
- `BMP_IMAGETYPE_IC` icon (monochrome)
- `BMP_IMAGETYPE_PT` pointer (monochrome)
Other than bitmap arrays (in which case `bmpread_load_info()` will have
returned `BMP_RESULT_ARRAY`) and the limitations that apply to icon and
pointer files (see below) the returned type is purely informational and no
special actions need to be taken.
### 1.6. Indexed BMPs
By default, bmplib will interpret indexed (color palette) BMPs and return the
image as 24-bit RGB data, same as non-indexed (RGB) BMPs.
@@ -113,8 +136,8 @@ If instead you want to keep the image as indexed, you have the option do so
with these two functions:
```c
int bmpread_num_palette_colors(BMPHANDLE h)
BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette)
int bmpread_num_palette_colors(BMPHANDLE h);
BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette);
```
`bmpread_num_palette_colors()` will return 0 for non-indexed images, otherwise
@@ -153,7 +176,7 @@ RGB data. After you loaded the palette, calls to `bmpread_dimensions
`bmpread_set_undefined()` will have no effect, as indexed images cannot have
an alpha channel (see below).
#### Undefined pixels
#### 1.6.1. Undefined pixels
RLE-encoded BMP files may have undefined pixels, either by using early
end-of-line or end-of-file codes, or by using delta codes to skip part of the
@@ -172,7 +195,7 @@ buffer, it will always be initialized to zero before loading the image). This
function has no effect on non-RLE BMPs.
```c
void bmpread_set_undefined(BMPHANDLE h, BMPUNDEFINED mode)
void bmpread_set_undefined(BMPHANDLE h, BMPUNDEFINED mode);
```
`mode` can be one of:
@@ -184,11 +207,11 @@ 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)
### ICC color profiles
### 1.7. ICC color profiles
```c
size_t bmpread_iccprofile_size(BMPHANDLE h)
BMPRESULT bmpread_load_iccprofile(BMPHANDLE h, unsigned char **pprofile)
size_t bmpread_iccprofile_size(BMPHANDLE h);
BMPRESULT bmpread_load_iccprofile(BMPHANDLE h, unsigned char **pprofile);
```
Use `bmpread_iccprofile_size()` to query the size (or existence) of an
@@ -196,7 +219,7 @@ embedded color profile. If the BMP file doesn't contain a profile, the return
value is 0.
bmplib does not interpret or apply embedded ICC color profiles. The profile is
simply returned 'as is', image data is not afected in any way.
simply returned 'as is', image data is not affected in any way.
`bmpread_load_iccprofile()` loads the profile into the buffer pointed to by
`*pprofile`. As with loading image and palette data, you can either allocate
@@ -210,11 +233,11 @@ if (bmpread_iccprofile_size(h) > 0)
```
### Optional settings for 64bit BMPs
### 1.8. Optional settings for 64bit BMPs
```c
int bmpread_is_64bit(BMPHANDLE h)
BMPRESULT bmpread_set_64bit_conv(BMPHANDLE h, BMPCONV64 conv)
int bmpread_is_64bit(BMPHANDLE h);
BMPRESULT bmpread_set_64bit_conv(BMPHANDLE h, BMPCONV64 conv);
```
If you don't do anything, 64bit BMPs will be read like any other BMP and the
@@ -238,19 +261,19 @@ Options for `bmpread_set_64bit()` are:
`BMP_FORMAT_S2_13`. Image values are returned exactly as they are in the BMP
file, without any conversion or attempt at interpretation.
### Setting a number format
### 1.9. 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:
```c
BMPRESULT bmp_set_number_format(BMPHANDLE h, BMPFORMAT format)
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()
### 1.10. Huge files: bmpread_set_insanity_limit()
bmplib will refuse to load images beyond a certain size (default 500MB) and
instead return `BMP_RESULT_INSANE`. If you want to load the image anyway, call
@@ -258,15 +281,78 @@ instead return `BMP_RESULT_INSANE`. If you want to load the image anyway, call
`limit` is the new allowed size in bytes (not MB!).
```c
void bmpread_set_insanity_limit(BMPHANDLE h, size_t limit)
void bmpread_set_insanity_limit(BMPHANDLE h, size_t limit);
```
### Load the image
### 1.11. OS/2 bitmap arrays (type 'BA')
#### bmpread_load_image()
Bitmap arrays are meant to contain several versions (with different
resolutions and color depths) of the same image. They are not meant to
contain different images like e.g. several scanned pages.
If a BMP file is of type 'BA', `bmpread_load_info()` will return
`BMP_RESULT_ARRAY`. Use the following functions to to query the number and
type of contained images and get a new `BMPHANDLE` for any of the contained
images:
```c
BMPRESULT bmpread_load_image(BMPHANDLE h, unsigned char **pbuffer)
int bmpread_array_num(BMPHANDLE h);
BMPRESULT bmpread_array_info(BMPHANDLE h, struct BmpArrayInfo *ai, int idx);
struct BmpArrayInfo {
BMPHANDLE handle;
BMPIMAGETYPE type;
int width, height;
int ncolors; /* 0 = RGB */
int screenwidth, screenheight; /* typically 0, or 1024x768 for 'hi-res' */
};
```
`bmpread_array_num()` returns the number of images contained in the array.
`bmpread_array_info()` fills the supplied `struct BmpArrayInfo` with
information about any of the contained images:
- `handle`: A handle that can be used with all the usual bmpread_* functions
to read the respective image. These handles for array images don't have to
be freed individually, it is sufficient to call `bmp_free()` on the
main array handle once the images have been loaded.
- `type`: Image type, one of
- `BMP_IMAGETYPE_BM` bitmap (normal image)
- `BMP_IMAGETYPE_CI` color icon
- `BMP_IMAGETYPE_CP` color pointer
- `BMP_IMAGETYPE_IC` icon (monochrome)
- `BMP_IMAGETYPE_PT` pointer (monochrome)
- `width, height`: Width and height of the image
- `ncolors`: number of colors (2/16/256) for indexed images, or 0 for RGB
images
- `screenwidth, screenheight`: The screen size the image was intended for.
Typically 0, or 1024x768 for 'hi-res'.
NOTE: You must not interleave calls to `bmpread_load_line()` for several array
images or simultanously call `bmpread_load_image()` from different threads.
First completely read one image before proceeding to the next one.
#### 1.10.1 OS/2 icons and pointers
OS/2 icons and pointers are often contained in bitmap arrays.
Some limitations apply for loading icons and pointers:
- The image is always returned as 8 bit per channel RGBA, loading index values
and palette is not supported.
- Maximum supported size is 512x512. This shouldn't be limiting for actual
legacy icons and pointers.
- For RLE images, undefined mode is always assumed to be
`BMP_UNDEFINED_LEAVE`, regardless of what `bmpread_set_undefined()` was set
to. (Because icons and pointers have their own mask-based transparency
scheme.)
### 1.12. Load the image
#### 1.12.1. bmpread_load_image()
```c
BMPRESULT bmpread_load_image(BMPHANDLE h, unsigned char **pbuffer);
```
Loads the complete image from the BMP file into the buffer pointed to by
@@ -300,10 +386,10 @@ If `bmpread_load_image()` returns `BMP_RESULT_TRUNCATED` or `BMP_RESULT_INVALID`
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()
#### 1.12.2. bmpread_load_line()
```c
BMPRESULT bmpread_load_line(BMPHANDLE h, unsigned char **pbuffer)
BMPRESULT bmpread_load_line(BMPHANDLE h, unsigned char **pbuffer);
```
Loads a single scan line from the BMP file into the buffer pointed to by
@@ -332,7 +418,7 @@ 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
### 1.13. Invalid pixels
Invalid pixels may occur in indexed BMPs, both RLE and non-RLE. Invalid pixels
either point beyond the given color palette, or they try to set pixels
@@ -344,26 +430,26 @@ 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
### 1.14. Query info about the BMP file
Note: these functions return information about the original BMP file being
read. They do *not* describe the format of the returned image data, which may
be different!
```c
BMPINFOVER bmpread_info_header_version(BMPHANDLE h)
int bmpread_info_header_size(BMPHANDLE h)
int bmpread_info_compression(BMPHANDLE h)
int bmpread_info_bitcount(BMPHANDLE h)
const char* bmpread_info_header_name(BMPHANDLE h)
const char* bmpread_info_compression_name(BMPHANDLE h)
BMPRESULT bmpread_info_channel_bits(BMPHANDLE h, int *r, int *g, int *b, int *a)
BMPINFOVER bmpread_info_header_version(BMPHANDLE h);
int bmpread_info_header_size(BMPHANDLE h);
int bmpread_info_compression(BMPHANDLE h);
int bmpread_info_bitcount(BMPHANDLE h);
const char* bmpread_info_header_name(BMPHANDLE h);
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
### 1.15. Release the handle
```c
void bmp_free(BMPHANDLE h)
void bmp_free(BMPHANDLE h);
```
Frees all resources associated with the handle `h`. **Image data is not
@@ -375,22 +461,22 @@ Note: Any error message strings returned by `bmp_errmsg()` are invalidated by
## 2. Functions for writing BMP files
### Get a handle
### 2.1. Get a handle
```c
BMPHANDLE bmpwrite_new(FILE *file)
BMPHANDLE bmpwrite_new(FILE *file);
```
### Set image dimensions
### 2.2. Set image dimensions
```c
BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
unsigned width,
unsigned height,
unsigned channels,
unsigned bitsperchannel)
unsigned bitsperchannel);
BMPRESULT bmpwrite_set_resolution(BMPHANDLE h, int xdpi, int ydpi)
BMPRESULT bmpwrite_set_resolution(BMPHANDLE h, int xdpi, int ydpi);
```
Note: the dimensions set with `bmpwrite_set_dimensions()` describe the source
@@ -398,7 +484,7 @@ 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
### 2.3. Set the output format
Optional: set the bit-depth for each output channel. bmplib will otherwise
choose appropriate bit-depths for your image. The bit-depth per channel can
@@ -406,14 +492,14 @@ be anywhere between 0 and 32, inclusive. In sum, the bits must be at least 1
and must not exceed 32.
```c
BMPRESULT bmpwrite_set_output_bits(BMPHANDLE h, int red, int green, int blue, int alpha)
BMPRESULT bmpwrite_set_output_bits(BMPHANDLE h, int red, int green, int blue, int alpha);
```
### Indexed images
### 2.4. Indexed images
```c
BMPRESULT bmpwrite_set_palette(BMPHANDLE h, int numcolors, unsigned char *palette)
BMPRESULT bmpwrite_allow_2bit(BMPHANDLE h)
BMPRESULT bmpwrite_set_palette(BMPHANDLE h, int numcolors, unsigned char *palette);
BMPRESULT bmpwrite_allow_2bit(BMPHANDLE h);
```
You can write 1/2/4/8-bit indexed images by providing a color palette with
@@ -433,12 +519,12 @@ relict), as many readers will refuse to open these. If you do want a 2-bit
BMP for 3- or 4-color images, call `bmpwrite_allow_2bit()` before calling
`bmpwrite_save_image()`.
#### RLE
#### 2.4.1. RLE
```c
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)
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 as run-length-encoded (RLE) bitmaps.
@@ -459,7 +545,7 @@ of the following values:
- `BMP_RLE_RLE8` use RLE8, regardless of number of colors in palette
#### 1-D Huffman encoding
#### 2.4.2. 1-D Huffman encoding
In order to write 1-D Huffman encoded bitmpas,
- the provided palette must have 2 colors,
@@ -476,7 +562,7 @@ runs of background color. This will not change the appearance of the
image, but setting it correctly will result in better compression.
#### RLE24
#### 2.4.3. RLE24
RLE24 is an old OS/2 compression method. As the name suggests, it's 24-bit RLE
for non-indexed images. Like Huffman encoding, it's very uncommon and only
@@ -487,7 +573,8 @@ 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
### 2.5. top-down / bottom-up
By default, bmplib will write BMP files bottom-up, which is how BMP files are
usually orientated.
@@ -496,7 +583,7 @@ For non-RLE files, you have the option to change the orientation to top-down.
(RLE files always have to be written in the default bottom-up orientation.)
```c
BMPRESULT bmpwrite_set_orientation(BMPHANDLE h, BMPORIENT orientation)
BMPRESULT bmpwrite_set_orientation(BMPHANDLE h, BMPORIENT orientation);
```
with `orientation` set to one of the following values:
@@ -513,12 +600,12 @@ provide the image lines in the order according to the orientation you have
chosen for the BMP file.
### ICC color profiles
### 2.6. ICC color profiles
```c
BMPRESULT bmpwrite_set_iccprofile(BMPHANDLE h, size_t size,
const unsigned char *iccprofile)
BMPRESULT bmpwrite_set_rendering_intent(BMPHANDLE h, BMPINTENT intent)
const unsigned char *iccprofile);
BMPRESULT bmpwrite_set_rendering_intent(BMPHANDLE h, BMPINTENT intent);
```
Use `bmpwrite_set_iccprofile()` to write an embedded ICC color profile to the
@@ -539,7 +626,7 @@ where `intent` is one of:
- `BMP_INTENT_ABS_COLORIMETRIC` (= absolute colorimetric)
### 64-bit RGBA BMPs
### 2.7. 64-bit RGBA BMPs
By default, bmplib will not write 64-bit BMPs because they are rather exotic
and hardly any software can open them.
@@ -547,7 +634,7 @@ and hardly any software can open them.
If you do want to write 64-bit BMPs, call
```c
BMPRESULT bmpwrite_set_64bit(BMPHANDLE h)
BMPRESULT bmpwrite_set_64bit(BMPHANDLE h);
```
In order to make use of the extended range available in 64-bit BMPs
@@ -559,11 +646,11 @@ 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
### 2.8. Write the image
```c
BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image);
BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line);
```
Write either the whole image at once with `bmpwrite_save_image()` or one line
@@ -583,10 +670,10 @@ set with `bmpwrite_set_orientation()` (see above).
## 3. General functions for both reading/writing BMPs
### bmp_free()
### 3.1. bmp_free()
```c
void bmp_free(BMPHANDLE h)
void bmp_free(BMPHANDLE h);
```
Frees all resources associated with the handle `h`. Image data is not
@@ -595,10 +682,10 @@ and still use the returned image data. Note: Any error messages returned
by `bmp_errmsg()` are invalidated by `bmp_free()` and cannot be used
anymore!
### bmp_errmsg()
### 3.2. bmp_errmsg()
```c
const char* bmp_errmsg(BMPHANDLE h)
const char* bmp_errmsg(BMPHANDLE h);
```
Returns a zero-terminated character string containing the last error
@@ -606,10 +693,10 @@ description(s). The returned string is safe to use until any other
bmplib-function is called with the same handle or the handle is freed with
`bmp_free()`.
### bmp_set_number_format()
### 3.3. bmp_set_number_format()
```c
BMPRESULT bmp_set_number_format(BMPHANDLE h, BMPFORMAT format)
BMPRESULT bmp_set_number_format(BMPHANDLE h, BMPFORMAT format);
```
sets the number format of the image buffer received from / passed to bmplib. `format` can be one of
@@ -623,18 +710,18 @@ 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()
### 3.4. bmp_version()
```c
const char* bmp_version(void)
const char* bmp_version(void);
```
Returns a zero-terminated character string containing the version of bmplib.
### bmp_set_huffman_t4black_value()
### 3.5. bmp_set_huffman_t4black_value()
```c
BMPRESULT bmp_set_huffman_t4black_value(BMPHANDLE h, int blackidx)
BMPRESULT bmp_set_huffman_t4black_value(BMPHANDLE h, int blackidx);
```
(not to be confused with `bmpwrite_set_huffman_img_fg_idx()`, which serves an
@@ -755,7 +842,7 @@ Used in `bmp_set_number_format()`. Possible values are:
## 5. Sample code
### Reading BMPs
### 5.1. Reading BMPs
```c
/* (all error checking left out for clarity) */
@@ -807,7 +894,7 @@ Used in `bmp_set_number_format()`. Possible values are:
*/
```
### Writing BMPs
### 5.2. Writing BMPs
```c
/* (all error checking left out for clarity) */

View File

@@ -13,10 +13,10 @@ For the complete API, refer to the *Full API Description* (API-full.md).
## 1. Reading BMP files:
```c
bmpread_new()
bmpread_dimensions()
bmpread_load_image()
bmp_free()
bmpread_new();
bmpread_dimensions();
bmpread_load_image();
bmp_free();
```
### Get a handle
@@ -39,7 +39,7 @@ BMPRESULT bmpread_dimensions(BMPHANDLE h,
int *height,
int *channels,
int *bitsperchannel,
BMPORIENT *orientation)
BMPORIENT *orientation);
```
Use `bmpread_dimensions()` to get all dimensions with one call. The return
@@ -56,7 +56,7 @@ line-by-line. Can be set to NULL. (see *Full API Description*)
### Load the image
```c
BMPRESULT bmpread_load_image(BMPHANDLE h, unsigned char **pbuffer)
BMPRESULT bmpread_load_image(BMPHANDLE h, unsigned char **pbuffer);
```
Loads the complete image from the BMP file into the buffer pointed to by
@@ -96,7 +96,7 @@ data is loaded anyway as far as possible and may be partially usable.
### Release the handle
```c
void bmp_free(BMPHANDLE h)
void bmp_free(BMPHANDLE h);
```
Frees all resources associated with the handle `h`. **Image data is not
@@ -111,16 +111,16 @@ Note: Any error message strings returned by `bmp_errmsg()` are invalidated by
## 2. Writing BMP files:
```c
bmpwrite_new()
bmpwrite_set_dimensions()
bmpwrite_save_image()
bmp_free()
bmpwrite_new();
bmpwrite_set_dimensions();
bmpwrite_save_image();
bmp_free();
```
### Get a handle
```c
BMPHANDLE bmpwrite_new(FILE *file)
BMPHANDLE bmpwrite_new(FILE *file);
```
### Set image dimensions
@@ -130,7 +130,7 @@ BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
unsigned width,
unsigned height,
unsigned channels,
unsigned bitsperchannel)
unsigned bitsperchannel);
```
Note: the dimensions set with `bmpwrite_set_dimensions()` describe the source
@@ -144,7 +144,7 @@ API description*)
### Write the image
```c
BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image);
```
Write the whole image at once with `bmpwrite_save_image()`.
@@ -159,7 +159,7 @@ file will be bottom-up.)
### bmp_free()
```c
void bmp_free(BMPHANDLE h)
void bmp_free(BMPHANDLE h);
```
Frees all resources associated with the handle `h`.

View File

@@ -8,7 +8,7 @@
Download [bmplib on github](https://github.com/rupertwh/bmplib).
## Current status (v1.7.6):
## Current status (v1.8.0):
### Reading BMP files:
- 16/24/32 bit RGB(A) with any bits/channel combination
(BI_RGB, BI_BITFIELDS, BI_ALPHABITFIELDS).
@@ -16,6 +16,8 @@ Download [bmplib on github](https://github.com/rupertwh/bmplib).
- 1/2/4/8 bit indexed (palette), including RLE4 and RLE8 compressed.
- RLE24 compressed (OS/2).
- Huffman encoded (OS/2).
- OS/2 bitmap arrays.
- OS/2 icons and pointers.
- optional line-by-line reading of BMPs.
- optionally return image data as float or s2.13 fixed point.
@@ -30,7 +32,7 @@ Download [bmplib on github](https://github.com/rupertwh/bmplib).
BMP_RESULT_PNG and leave the file pointer in the correct state to be
passed on to either libpng or libjpeg. Works as designed. Don't want to
create dependency on those libs.
- We currently ignore icc-profiles and chromaticity/gamma values. See TODO.
- We currently ignore chromaticity/gamma values from V4+ headers. See TODO.
### Writing BMP files:
@@ -134,31 +136,14 @@ Microsoft tools, the new GIMP 3.0 is the only one I am aware of). Use
## TODOs:
### Definitely:
- [x] write indexed images.
- [x] write RLE-compressed images ~~(RLE4/RLE8 only. No OS/2 v2 BMPs)~~.
- [x] read RLE24-encoded BMPs.
- [x] read Huffman-encoded BMPs. (Still haven't found any real-life examples)
- [x] line-by-line reading/writing. ~~Right now, the image can only be
passed as a whole to/from bmplib.~~
- [x] read/write icc-profile and chromaticity/gamma values
- [x] sanity checks for size of of image / palette. Require confirmation
above a certain size (~ 500MB?)
- [x] store undefined pixels (RLE delta and early EOL/EOF) as alpha
- [ ] read/write chromaticity/gamma values
### Maybe:
- [x] passing indexed data and palette to user (optionally) instead of
RGB-data.
- [ ] interpret icc-profile, to enable giving at least sRGB/not-sRGB info.
(Like sRGB / probably-sRGB / maybe-sRGB). Torn on that one, would need
dependency on liblcms2.
- [ ] "BA"-files (bitmap-arrays). Either return the first bitmap only
(which is the 'official' default) or let user pick one/multiple/all to
be read in sequence.
- [ ] Add a 'not-a-BMP-file' return type instead of just returning error.
- ~~[ ] icon- and pointer-files ("CI", "CP", "IC", "PT").~~
- [x] 64-bits BMPs. (I changed my mind)
### Unclear:
@@ -167,19 +152,6 @@ Microsoft tools, the new GIMP 3.0 is the only one I am aware of). Use
platforms/cpus. And Windows?
### Non-feature (internal):
- [x] complete API description (see API-full.md and API-quick-start.md)
- [x] bmp-read.c is getting too big, split into several files
## Misc:
- [x] License: probably LPGL3? That's what I'm going with for now.
Cheers,
Rupert

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-common.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -320,18 +320,11 @@ bool cm_is_one_of(int n, int candidate, ...)
}
int cm_align4padding(unsigned long long a)
{
return (int) (cm_align4size(a) - a);
}
int cm_align2padding(unsigned long long a)
{
return (int) (cm_align2size(a) - a);
}
/*********************************************************
* endianess-agnostic functions to read/write
@@ -402,7 +395,10 @@ bool read_s16_le(FILE *file, int16_t *val)
if (!read_u16_le(file, &u16))
return false;
*val = (int16_t)u16;
if (u16 >= 0x8000U)
*val = (int16_t)(u16 - 0x8000U) - 32767 - 1;
else
*val = (int16_t)u16;
return true;
}
@@ -414,7 +410,10 @@ bool read_s32_le(FILE *file, int32_t *val)
if (!read_u32_le(file, &u32))
return false;
*val = (int32_t)u32;
if (u32 >= 0x80000000UL)
*val = (int32_t)(u32 - 0x80000000UL) - 0x7fffffffL - 1;
else
*val = (int32_t)u32;
return true;
}
@@ -441,3 +440,27 @@ int16_t s16_from_le(const unsigned char *buf)
{
return (int16_t)u16_from_le(buf);
}
/*****************************************************************************
* cm_infoheader_name
*****************************************************************************/
const char* cm_infoheader_name(enum BmpInfoVer infoversion)
{
switch (infoversion) {
case BMPINFO_CORE_OS21 : return "OS21XBITMAPHEADER";
case BMPINFO_OS22 : return "OS22XBITMAPHEADER";
case BMPINFO_V3 : return "BITMAPINFOHEADER";
case BMPINFO_V3_ADOBE1 : return "BITMAPINFOHEADER + RGB mask";
case BMPINFO_V3_ADOBE2 : return "BITMAPINFOHEADER + RGBA mask";
case BMPINFO_V4 : return "BITMAPV4HEADER";
case BMPINFO_V5 : return "BITMAPV5HEADER";
case BMPINFO_FUTURE : return "unknown future version";
default:
return "invalid infoheader version";
}
}

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-common.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -95,6 +95,16 @@ struct Bmpcommon {
bool huffman_black_is_zero; /* defaults to false */
};
enum ReadState {
RS_INIT,
RS_EXPECT_ICON_MASK,
RS_HEADER_OK,
RS_DIMENSIONS_QUERIED,
RS_LOAD_STARTED,
RS_LOAD_DONE,
RS_ARRAY,
RS_FATAL,
};
struct Bmpread {
struct Bmpcommon c;
@@ -102,39 +112,46 @@ struct Bmpread {
size_t bytes_read; /* number of bytes we have read from the file */
struct Bmpfile *fh;
struct Bmpinfo *ih;
struct Arraylist *arrayimgs;
int narrayimgs;
bool is_arrayimg;
unsigned int insanity_limit;
int width;
int height;
enum BmpOrient orientation;
bool is_icon;
bool icon_is_mono;
bool has_alpha; /* original BMP has alpha channel */
enum BmpUndefined undefined_mode;
bool we_allocated_buffer;
bool line_by_line;
struct Palette *palette;
struct Colormask cmask;
unsigned char *icon_mono_and;
unsigned char *icon_mono_xor;
int icon_mono_width;
int icon_mono_height;
/* result image dimensions */
enum Bmpconv64 conv64;
bool conv64_explicit;
int result_channels;
bool result_indexed;
int result_bits_per_pixel;
int result_bytes_per_pixel;
int result_bitsperchannel;
enum BmpFormat result_format;
bool result_format_explicit;
size_t result_size;
bool conv64_explicit;
bool result_indexed;
bool result_format_explicit;
/* state */
unsigned long lasterr;
bool getinfo_called;
enum ReadState read_state;
int getinfo_return;
bool jpeg;
bool png;
bool dimensions_queried;
bool dim_queried_width;
bool dim_queried_height;
bool dim_queried_channels;
bool dim_queried_bitsperchannel;
bool image_loaded;
bool iccprofile_size_queried;
bool rle;
bool rle_eol;
bool rle_eof;
@@ -150,9 +167,15 @@ struct Bmpread {
bool file_err;
bool file_eof;
bool panic;
};
enum WriteState {
WS_INIT,
WS_DIMENSIONS_SET,
WS_SAVE_STARTED,
WS_SAVE_DONE,
WS_FATAL,
};
struct Bmpwrite {
struct Bmpcommon c;
@@ -166,6 +189,7 @@ struct Bmpwrite {
int source_bitsperchannel;
int source_bytes_per_pixel;
enum BmpFormat source_format;
bool source_has_alpha;
struct Palette *palette;
int palette_size; /* sizeof palette in bytes */
unsigned char *iccprofile;
@@ -173,26 +197,22 @@ struct Bmpwrite {
/* output */
uint64_t bytes_written;
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 */
bool allow_2bit; /* Windows CE, but many will not read it */
bool allow_huffman;
bool allow_rle24;
int rle; /* 1, 4, 8, or 24 */
bool allow_2bit; /* Windows CE */
bool allow_huffman; /* OS/2 */
bool allow_rle24; /* OS/2 */
bool out64bit;
int outbytes_per_pixel;
int padding;
int *group;
int group_count;
/* state */
enum WriteState write_state;
bool outbits_set;
bool dimensions_set;
bool saveimage_started;
bool saveimage_done;
bool line_by_line;
int lbl_y;
uint32_t hufbuf;
int hufbuf_len;
@@ -217,10 +237,8 @@ bool cm_all_equal_int(int n, ...);
bool cm_all_positive_int(int n, ...);
bool cm_is_one_of(int n, int candidate, ...);
#define cm_align4size(a) ((((a) + 3) >> 2) << 2)
#define cm_align2size(a) ((((a) + 1) >> 1) << 1)
#define cm_align4size(a) (((a) + 3) & ~3ULL)
int cm_align4padding(unsigned long long a);
int cm_align2padding(unsigned long long a);
int cm_count_bits(unsigned long v);
bool cm_gobble_up(BMPREAD_R rp, int count);
@@ -245,6 +263,8 @@ int32_t s32_from_le(const unsigned char *buf);
uint16_t u16_from_le(const unsigned char *buf);
int16_t s16_from_le(const unsigned char *buf);
const char* cm_infoheader_name(enum BmpInfoVer infoversion);
#define HMAGIC_READ 0x44414552UL
#define HMAGIC_WRITE 0x54495257UL
@@ -321,6 +341,14 @@ struct Bmpinfo {
enum BmpInfoVer version;
};
struct Bmparray {
uint16_t type;
uint32_t size;
uint32_t offsetnext;
uint16_t screenwidth;
uint16_t screenheight;
};
#define IH_PROFILEDATA_OFFSET (14L + 112L)
#define MAX_ICCPROFILE_SIZE (1UL << 20)

360
bmp-read-icons.c Normal file
View File

@@ -0,0 +1,360 @@
/* bmplib - bmp-read-icons.c
*
* Copyright (c) 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>
#define BMPLIB_LIB
#include "config.h"
#include "bmplib.h"
#include "logging.h"
#include "bmp-common.h"
#include "bmp-read-icons.h"
/********************************************************
* bmpread_array_num
*******************************************************/
API int bmpread_array_num(BMPHANDLE h)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state != RS_ARRAY) {
logerr(rp->c.log, "Not a bitmap array");
return -1;
}
return rp->narrayimgs;
}
/********************************************************
* bmpread_array_info
*******************************************************/
API BMPRESULT bmpread_array_info(BMPHANDLE h, struct BmpArrayInfo *ai, int idx)
{
BMPREAD rp;
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->read_state != RS_ARRAY) {
logerr(rp->c.log, "Not a bitmap array");
return BMP_RESULT_ERROR;
}
if (idx < 0 || idx >= rp->narrayimgs) {
logerr(rp->c.log, "Invalid array index %d. Max is %d", idx, rp->narrayimgs - 1);
return BMP_RESULT_ERROR;
}
if (!ai) {
logerr(rp->c.log, "Invalid array info pointer (NULL)");
return BMP_RESULT_ERROR;
}
struct Arraylist *img = &rp->arrayimgs[idx];
BMPREAD imgrp = (BMPREAD)img->handle;
memset(ai, 0, sizeof *ai);
ai->type = imgrp->fh->type;
ai->handle = img->handle;
ai->width = imgrp->width;
ai->height = imgrp->height;
if (imgrp->ih->bitcount <= 8)
ai->ncolors = 1 << imgrp->ih->bitcount;
else
ai->ncolors = 0;
ai->screenwidth = img->ah.screenwidth;
ai->screenheight = img->ah.screenheight;
return BMP_RESULT_OK;
}
/********************************************************
* icon_read_array
*******************************************************/
static bool s_read_array_header(BMPREAD_R rp, struct Bmparray *ah);
static void s_array_header_from_file_header(struct Bmparray *ah, struct Bmpfile *fh);
bool icon_read_array(BMPREAD_R rp)
{
struct Arraylist *imgs = NULL;
struct Bmparray ah = { 0 };
int n = 0;
const int nmax = 16;
bool invalid = false;
if (!(imgs = calloc(nmax, sizeof *imgs))) {
logsyserr(rp->c.log, "Allocating bitmap array list");
rp->lasterr = BMP_ERR_MEMORY;
return false;
}
s_array_header_from_file_header(&ah, rp->fh);
while (n < nmax) {
if (ah.type != BMPFILE_BA) {
logerr(rp->c.log, "Invalid BMP type (0x%04x), expected 'BA'", (unsigned) ah.type);
invalid = true;
rp->lasterr = BMP_ERR_HEADER;
break;
}
memcpy(&imgs[n].ah, &ah, sizeof ah);
imgs[n].handle = bmpread_new(rp->file);
if (imgs[n].handle) {
if (BMP_RESULT_OK == bmpread_load_info(imgs[n].handle)) {
((BMPREAD)imgs[n].handle)->is_arrayimg = true;
n++;
} else {
bmp_free(imgs[n].handle);
invalid = true;
rp->lasterr = BMP_ERR_HEADER;
break;
}
} else {
logerr(rp->c.log, "Failed to create handle for array image");
invalid = true;
rp->lasterr = BMP_ERR_MEMORY;
break;
}
if (!ah.offsetnext)
break;
#if ( LONG_MAX <= 0x7fffffffL )
if (ah.offsetnext > (unsigned long)LONG_MAX) {
logerr(rp->c.log, "Invalid offset to next array image: %lu", (unsigned long)ah.offsetnext);
invalid = true;
rp->lasterr = BMP_ERR_HEADER;
break;
}
#endif
if (fseek(rp->file, ah.offsetnext, SEEK_SET)) {
logsyserr(rp->c.log, "Seeking next array header");
invalid = true;
rp->lasterr = BMP_ERR_FILEIO;
break;
}
if (!s_read_array_header(rp, &ah)) {
invalid = true;
break;
}
}
rp->arrayimgs = imgs;
rp->narrayimgs = n;
return !invalid;
}
/********************************************************
* s_read_array_header
*******************************************************/
static bool s_read_array_header(BMPREAD_R rp, struct Bmparray *ah)
{
if (read_u16_le(rp->file, &ah->type) &&
read_u32_le(rp->file, &ah->size) &&
read_u32_le(rp->file, &ah->offsetnext) &&
read_u16_le(rp->file, &ah->screenwidth) &&
read_u16_le(rp->file, &ah->screenheight)) {
return true;
}
if (feof(rp->file)) {
logerr(rp->c.log, "unexpected end-of-file while reading "
"array header");
rp->lasterr = BMP_ERR_TRUNCATED;
} else {
logsyserr(rp->c.log, "error reading array header");
rp->lasterr = BMP_ERR_FILEIO;
}
return false;
}
/********************************************************
* s_array_header_from_file_header
*******************************************************/
static void s_array_header_from_file_header(struct Bmparray *ah, struct Bmpfile *fh)
{
ah->type = fh->type;
ah->size = fh->size;
ah->offsetnext = (uint32_t)fh->reserved2 << 16 | fh->reserved1;
ah->screenwidth = fh->offbits & 0xffff;
ah->screenheight = (fh->offbits >> 16) & 0xffff;
}
/********************************************************
* icon_load_masks
*******************************************************/
long icon_load_masks(BMPREAD_R rp)
{
/* OS/2 icons and pointers contain 1-bit AND and XOR masks, stacked in a single
* image. For monochrome (IC/PT), that's all the image; for color (CI/CP), these are
* followed by a complete color image (including headers), the masks are only used
* for transparency information.
*/
BMPHANDLE hmono = NULL;
BMPREAD rpmono;
unsigned char *monobuf = NULL;
size_t bufsize;
unsigned bmptype = rp->fh->type;
long posmono = 0, poscolor = 0;
if (fseek(rp->file, -14, SEEK_CUR)) {
logsyserr(rp->c.log, "Seeking to start of icon/pointer");
goto abort;
}
if (-1 == (posmono = ftell(rp->file))) {
logsyserr(rp->c.log, "Saving file position");
goto abort;
}
/* first, load monochrome XOR/AND bitmap. We'll use the
* AND bitmap as alpha channel for the color bitmap
*/
if (!(hmono = bmpread_new(rp->file))) {
logerr(rp->c.log, "Getting handle for monochrome XOR/AND map");
goto abort;
}
rpmono = cm_read_handle(hmono);
rpmono->read_state = RS_EXPECT_ICON_MASK;
if (BMP_RESULT_OK != bmpread_load_info(hmono)) {
logerr(rp->c.log, "%s", bmp_errmsg(hmono));
goto abort;
}
if (rpmono->fh->type != bmptype) {
logerr(rp->c.log, "File type mismatch. Have 0x%04x, expected 0x%04x",
(unsigned)rpmono->fh->type, bmptype);
}
if (rp->fh->type == BMPFILE_CI || rp->fh->type == BMPFILE_CP) {
if (-1 == (poscolor = ftell(rp->file))) {
logsyserr(rp->c.log, "Saving position of color header");
goto abort;
}
}
if (!(rpmono->width > 0 && rpmono->height > 0 && rpmono->width <=512 && rpmono->height <= 512)) {
logerr(rp->c.log, "Invalid icon/pointer dimensions: %dx%d", rpmono->width, rpmono->height);
goto abort;
}
if (rpmono->ih->bitcount != 1) {
logerr(rp->c.log, "Invalid icon/pointer monochrome bitcount: %d", rpmono->ih->bitcount);
goto abort;
}
if (rpmono->height & 1) {
logerr(rp->c.log, "Invalid odd icon/pointer height: %d (must be even)", rpmono->height);
goto abort;
}
int width, height, bitsperchannel, channels;
if (BMP_RESULT_OK != bmpread_dimensions(hmono, &width, &height, &channels, &bitsperchannel, NULL)) {
logerr(rp->c.log, "%s", bmp_errmsg(hmono));
goto abort;
}
height /= 2; /* mochrome contains two stacked bitmaps (AND and XOR) */
if (channels != 3 || bitsperchannel != 8) {
logerr(rp->c.log, "Unexpected result color depth for monochrome image: "
"%d channels, %d bits/channel", channels, bitsperchannel);
goto abort;
}
/* store the AND/XOR bitmaps in the main BMPREAD struct */
bufsize = bmpread_buffersize(hmono);
if (BMP_RESULT_OK != bmpread_load_image(hmono, &monobuf)) {
logerr(rp->c.log, "%s", bmp_errmsg(hmono));
goto abort;
}
if (!(bufsize > 0 && monobuf != NULL)) {
logerr(rp->c.log, "Panic! unkown error while loading monochrome bitmap");
goto abort;
}
if (!(rp->icon_mono_and = malloc(width * height))) {
logsyserr(rp->c.log, "Allocating mono AND bitmap");
goto abort;
}
if (!(rp->icon_mono_xor = malloc(width * height))) {
logsyserr(rp->c.log, "Allocating mono XOR bitmap");
goto abort;
}
for (int i = 0; i < width * height; i++)
rp->icon_mono_and[i] = 255 - monobuf[3 * i];
for (int i = 0; i < width * height; i++)
rp->icon_mono_xor[i] = monobuf[3 * (width * height + i)];
rp->icon_mono_width = width;
rp->icon_mono_height = height;
free(monobuf);
monobuf = NULL;
bmp_free(hmono);
hmono = NULL;
if (rp->fh->type == BMPFILE_CI || rp->fh->type == BMPFILE_CP)
return poscolor;
return posmono;
abort:
if (hmono)
bmp_free(hmono);
if (monobuf)
free(monobuf);
return -1;
}

28
bmp-read-icons.h Normal file
View File

@@ -0,0 +1,28 @@
/* bmplib - bmp-read-icons.h
*
* Copyright (c) 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/>
*/
struct Arraylist {
struct Bmparray ah;
BMPHANDLE handle;
};
long icon_load_masks(BMPREAD_R rp);
bool icon_read_array(BMPREAD_R rp);

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-read-loadimage.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include <assert.h>
#include <math.h>
@@ -65,7 +66,7 @@ static void s_log_error_from_state(BMPREAD_R rp);
static bool s_cont_error(BMPREAD_R rp);
static bool s_stopping_error(BMPREAD_R rp);
static inline int s_read_one_byte(BMPREAD_R rp);
static inline void s_int_to_result_format(BMPREAD_R rp, int frombits, unsigned char *restrict px);
static inline void s_int8_to_result_format(BMPREAD_R rp, const int *restrict fromrgba, unsigned char *restrict px);
static BMPRESULT s_load_image_or_line(BMPREAD_R rp, unsigned char **restrict buffer, bool line_by_line);
static void s_read_rgb_line(BMPREAD_R rp, unsigned char *restrict line);
@@ -73,9 +74,10 @@ static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line);
static void s_read_rle_line(BMPREAD_R rp, unsigned char *restrict line,
int *restrict x, int *restrict yoff);
static void s_read_huffman_line(BMPREAD_R rp, unsigned char *restrict line);
static bool s_are_settings_icon_compatible(BMPREAD_R rp);
_Static_assert(sizeof(float) == 4, "sizeof(float) must be 4. Cannot build bmplib.");
_Static_assert(sizeof(int) >= 4, "int must be at least 32bit. Cannot build bmplib.");
static_assert(sizeof(float) == 4, "sizeof(float) must be 4. Cannot build bmplib.");
static_assert(sizeof(int) * CHAR_BIT >= 32, "int must be at least 32bit. Cannot build bmplib.");
/********************************************************
@@ -124,33 +126,48 @@ static BMPRESULT s_load_image_or_line(BMPREAD_R rp, unsigned char **restrict buf
{
size_t buffer_size;
if (!(rp->getinfo_called && (rp->getinfo_return == BMP_RESULT_OK))) {
if (rp->getinfo_return == BMP_RESULT_INSANE) {
logerr(rp->c.log, "trying to load insanley large image");
return BMP_RESULT_INSANE;
}
logerr(rp->c.log, "getinfo had failed, cannot load image");
if (rp->read_state == RS_FATAL) {
logerr(rp->c.log, "Cannot load image due to a previous fatal error");
return BMP_RESULT_ERROR;
}
if (rp->image_loaded) {
if (rp->read_state >= RS_ARRAY) {
logerr(rp->c.log, "Invalid operation on bitmap array");
return BMP_RESULT_ERROR;
}
if (rp->read_state >= RS_LOAD_DONE) {
logerr(rp->c.log, "Cannot load image more than once!");
return BMP_RESULT_ERROR;
}
if (rp->line_by_line && !line_by_line) {
if (rp->read_state >= RS_LOAD_STARTED && !line_by_line) {
logerr(rp->c.log, "Image is being loaded line-by-line. "
"Cannot switch to full image.");
"Cannot switch to full image.");
return BMP_RESULT_ERROR;
}
if (!rp->dimensions_queried) {
logerr(rp->c.log, "must query dimensions before loading image");
if (rp->read_state < RS_DIMENSIONS_QUERIED) {
logerr(rp->c.log, "Must query dimensions before loading image");
return BMP_RESULT_ERROR;
}
if (rp->getinfo_return == BMP_RESULT_INSANE) {
logerr(rp->c.log, "trying to load insanley large image");
return BMP_RESULT_INSANE;
}
if (rp->read_state < RS_LOAD_STARTED && rp->is_icon) {
if (!s_are_settings_icon_compatible(rp)) {
logerr(rp->c.log, "Panic! Trying to load icon/pointer with incompatibele settings.\n");
rp->read_state = RS_FATAL;
rp->lasterr = BMP_ERR_INTERNAL;
return BMP_RESULT_ERROR;
}
}
if (!buffer) {
logerr(rp->c.log, "buffer pointer is NULL");
logerr(rp->c.log, "Buffer pointer is NULL. (It may point to a NULL pointer, but must not itself be NULL)");
return BMP_RESULT_ERROR;
}
@@ -171,34 +188,30 @@ static BMPRESULT s_load_image_or_line(BMPREAD_R rp, unsigned char **restrict buf
if (rp->we_allocated_buffer || (rp->rle && (rp->undefined_mode == BMP_UNDEFINED_TO_ALPHA)))
memset(*buffer, 0, buffer_size);
if (!line_by_line)
rp->image_loaded = true; /* point of no return */
if (!rp->line_by_line) { /* either whole image or first line */
if (rp->read_state < RS_LOAD_STARTED) { /* either whole image or first line */
if (rp->bytes_read > rp->fh->offbits) {
logerr(rp->c.log, "Corrupt file");
logerr(rp->c.log, "Corrupt file, invalid offset to image bitmap data");
goto abort;
}
/* skip to actual bitmap data: */
if (!cm_gobble_up(rp, rp->fh->offbits - rp->bytes_read)) {
/*if (!cm_gobble_up(rp, rp->fh->offbits - rp->bytes_read)) {*/
if (fseek(rp->file, (long)rp->fh->offbits, SEEK_SET)) {
logerr(rp->c.log, "while seeking start of bitmap data");
goto abort;
}
rp->bytes_read += rp->fh->offbits - rp->bytes_read;
rp->read_state = RS_LOAD_STARTED;
}
if (line_by_line) {
rp->line_by_line = true; /* don't set this earlier, or we won't */
/* be able to identify first line */
if (line_by_line)
s_read_one_line(rp, *buffer);
} else {
else
s_read_whole_image(rp, *buffer);
}
s_log_error_from_state(rp);
if (s_stopping_error(rp)) {
rp->truncated = true;
rp->image_loaded = true;
rp->read_state = RS_FATAL;
return BMP_RESULT_TRUNCATED;
} else if (s_cont_error(rp))
return BMP_RESULT_INVALID;
@@ -210,10 +223,42 @@ abort:
free(*buffer);
*buffer = NULL;
}
rp->image_loaded = true;
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
static bool s_are_settings_icon_compatible(BMPREAD_R rp)
{
/* some catch-all sanity checks for icons/pointers. Strictly, these
* shouldn't be necessary, as they should have been caught already.
* If any of these fail, there is a bug somewhere else.
*/
if (rp->result_channels != 4 || rp->result_bitsperchannel != 8)
return false;
if (rp->result_format != BMP_FORMAT_INT)
return false;
if (rp->rle && (rp->undefined_mode != BMP_UNDEFINED_LEAVE))
return false;
if (!(rp->rle || (rp->ih->compression == BI_RGB)))
return false;
return true;
}
/********************************************************
* apply_icon_alpha
*******************************************************/
static void apply_icon_alpha(BMPREAD_R rp, int y, unsigned char *restrict line)
{
for (int x = 0; x < rp->width; x++) {
line[x * 4 + 3] = rp->icon_mono_and[(rp->height - y - 1) * rp->width + x];
}
}
/********************************************************
@@ -240,6 +285,7 @@ static void s_read_whole_image(BMPREAD_R rp, unsigned char *restrict image)
/********************************************************
* s_read_one_line
*******************************************************/
static void s_read_monoicon_line(BMPREAD_R rp, unsigned char *restrict line, int y);
static void s_read_one_line(BMPREAD_R rp, unsigned char *restrict line)
{
@@ -258,6 +304,8 @@ static void s_read_one_line(BMPREAD_R rp, unsigned char *restrict line)
} else {
if (rp->ih->compression == BI_OS2_HUFFMAN) {
s_read_huffman_line(rp, line);
} else if (rp->is_icon && rp->icon_is_mono) {
s_read_monoicon_line(rp, line, rp->lbl_y);
} else {
s_read_indexed_line(rp, line);
}
@@ -277,13 +325,31 @@ static void s_read_one_line(BMPREAD_R rp, unsigned char *restrict line)
s_read_rgb_line(rp, line);
}
if (rp->is_icon) {
apply_icon_alpha(rp, rp->lbl_y, line);
}
rp->lbl_y++;
if (rp->lbl_y >= rp->height) {
rp->image_loaded = true;
rp->read_state = RS_LOAD_DONE;
}
}
/********************************************************
* s_read_monoicon_line
*******************************************************/
static void s_read_monoicon_line(BMPREAD_R rp, unsigned char *restrict line, int y)
{
for (int x = 0; x < rp->width; x++) {
for (int c = 0; c < 3; c++) {
line[rp->result_bytes_per_pixel * x + c] =
rp->icon_mono_xor[(rp->height - y - 1) * rp->width + x];
}
}
}
/********************************************************
* s_read_rgb_line
@@ -291,9 +357,10 @@ static void s_read_one_line(BMPREAD_R rp, unsigned char *restrict line)
static inline bool s_read_rgb_pixel(BMPREAD_R rp, union Pixel *restrict px);
static inline double s_s2_13_to_float(uint16_t s2_13);
static inline uint16_t s_float_to_s2_13(double d);
static inline double s_int_to_float(unsigned long ul, int bits);
static inline void s_convert64(uint16_t *val64);
static inline void s_convert64srgb(uint16_t *val64);
static inline void s_convert64(uint16_t val64[static 4]);
static inline void s_convert64srgb(uint16_t val64[static 4]);
static inline double s_srgb_gamma_float(double d);
static inline uint16_t s_srgb_gamma_s2_13(uint16_t s2_13);
@@ -376,8 +443,7 @@ static void s_read_rgb_line(BMPREAD_R rp, unsigned char *restrict line)
} else {
for (i = 0; i < rp->result_channels; i++) {
d = s_int_to_float(px.value[i], rp->cmask.bits.value[i]);
d = d * 8192.0 + 0.5;
((uint16_t*)line)[offs+i] = (uint16_t) d;
((uint16_t*)line)[offs+i] = s_float_to_s2_13(d);
}
}
break;
@@ -399,23 +465,41 @@ static void s_read_rgb_line(BMPREAD_R rp, unsigned char *restrict line)
static inline double s_s2_13_to_float(uint16_t s2_13)
{
return (int16_t)s2_13 / 8192.0;
int16_t s16;
if (s2_13 >= 0x8000)
s16 = (int16_t)(s2_13 - 0x8000) - 0x7fff - 1;
else
s16 = (int16_t)s2_13;
return s16 / 8192.0;
}
static inline uint16_t s_float_to_s2_13(double d)
{
d = MIN(d, 3.99987793);
d = MAX(-4.0, d);
return (uint16_t) ((int)round(d * 8192.0) & 0xffff);
}
static inline double s_int_to_float(unsigned long ul, int bits)
{
return (double) ul / ((1ULL<<bits)-1);
return (double) ul / ((1ULL << bits) - 1);
}
static inline void s_convert64(uint16_t *val64)
static inline void s_convert64(uint16_t val64[static 4])
{
/* convert the s2.13 values of a 64bit BMP to plain old 16bit integers.
* Values are clipped to the representable [0..1] range
*/
double d;
for (int i = 0; i < 4; i++) {
d = (double) (int16_t) val64[i];
d /= 8192.0;
d = s_s2_13_to_float(val64[i]);
d = MAX(0.0, d);
d = MIN(d, 1.0);
val64[i] = (uint16_t) (d * 0xffff + 0.5);
@@ -423,13 +507,14 @@ static inline void s_convert64(uint16_t *val64)
}
static inline void s_convert64srgb(uint16_t *val64)
static inline void s_convert64srgb(uint16_t val64[static 4])
{
/* Same as s_convert64(), but also apply sRGB gamma. */
double d;
for (int i = 0; i < 4; i++) {
d = (double) (int16_t) val64[i];
d /= 8192.0;
d = s_s2_13_to_float(val64[i]);
d = MAX(0.0, d);
d = MIN(d, 1.0);
@@ -455,11 +540,11 @@ static inline double s_srgb_gamma_float(double d)
static inline uint16_t s_srgb_gamma_s2_13(uint16_t s2_13)
{
double d;
double d;
d = (int16_t)s2_13 / 8192.0;
d = s_s2_13_to_float(s2_13);
d = s_srgb_gamma_float(d);
return (uint16_t) (((int)(d * 8192.0 + 0.5)) & 0xffff);
return s_float_to_s2_13(d);
}
@@ -513,14 +598,13 @@ static inline bool s_buffer32_fill(BMPREAD_R rp, struct Buffer32 *restrict buf)
for (int i = 0; i < 4; i++) {
if (EOF == (byte = s_read_one_byte(rp))) {
s_set_file_error(rp);
return false;
break;
}
buf->buffer <<= 8;
buf->buffer |= byte;
buf->buffer |= ((uint32_t)byte) << (8 * (3 - i));
buf->n += 8;
}
buf->n = 32;
return true;
return buf->n > 0;
}
@@ -528,7 +612,8 @@ static inline uint32_t s_buffer32_bits(struct Buffer32 *restrict buf, int nbits)
{
uint32_t result;
assert(nbits < 32);
assert(nbits == 1 || nbits == 2 || nbits == 4 || nbits == 8);
assert(nbits <= buf->n);
result = buf->buffer >> (32 - nbits);
buf->buffer = (buf->buffer << nbits) & 0xffffffffUL;
@@ -540,9 +625,10 @@ static inline uint32_t s_buffer32_bits(struct Buffer32 *restrict buf, int nbits)
static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line)
{
int x = 0, v;
bool done = false;
struct Buffer32 buffer;
size_t offs;
int rgba[4] = { 0 };
bool done = false;
/* the buffer size of 32 bits takes care of padding bytes */
@@ -561,10 +647,10 @@ static void s_read_indexed_line(BMPREAD_R rp, unsigned char *restrict line)
if (rp->result_indexed) {
line[offs] = v;
} else {
line[offs] = rp->palette->color[v].red;
line[offs+1] = rp->palette->color[v].green;
line[offs+2] = rp->palette->color[v].blue;
s_int_to_result_format(rp, 8, line + offs);
rgba[0] = rp->palette->color[v].red;
rgba[1] = rp->palette->color[v].green;
rgba[2] = rp->palette->color[v].blue;
s_int8_to_result_format(rp, rgba, line + offs);
}
if (++x == rp->width) {
@@ -589,6 +675,7 @@ static void s_read_rle_line(BMPREAD_R rp, unsigned char *restrict line,
bool repeat = false, padding = false, odd = false;
int right, up;
int v, r = 0, g = 0, b = 0;
int rgba[4] = { 0, 0, 0, 0xff };
size_t offs;
int bits = rp->ih->bitcount;
@@ -622,14 +709,12 @@ static void s_read_rle_line(BMPREAD_R rp, unsigned char *restrict line,
}
offs = (size_t) *x * rp->result_bytes_per_pixel;
if ((rp->undefined_mode == BMP_UNDEFINED_TO_ALPHA) && !rp->result_indexed)
line[offs+3] = 0xff; /* set alpha to 1.0 for defined pixels */
switch (bits) {
case 24:
line[offs] = r;
line[offs+1] = g;
line[offs+2] = b;
s_int_to_result_format(rp, 8, line + offs);
rgba[0] = r;
rgba[1] = g;
rgba[2] = b;
s_int8_to_result_format(rp, rgba, line + offs);
break;
case 4:
case 8:
@@ -646,10 +731,10 @@ static void s_read_rle_line(BMPREAD_R rp, unsigned char *restrict line,
if (rp->result_indexed) {
line[offs] = v;
} else {
line[offs] = rp->palette->color[v].red;
line[offs+1] = rp->palette->color[v].green;
line[offs+2] = rp->palette->color[v].blue;
s_int_to_result_format(rp, 8, line + offs);
rgba[0] = rp->palette->color[v].red;
rgba[1] = rp->palette->color[v].green;
rgba[2] = rp->palette->color[v].blue;
s_int8_to_result_format(rp, rgba, line + offs);
}
break;
}
@@ -764,6 +849,7 @@ static void s_read_huffman_line(BMPREAD_R rp, unsigned char *restrict line)
{
size_t offs;
int x = 0, runlen;
int rgba[4] = { 0 };
bool black = false;
while (x < rp->width) {
@@ -802,10 +888,10 @@ static void s_read_huffman_line(BMPREAD_R rp, unsigned char *restrict line)
if (rp->result_indexed) {
line[offs] = black ^ rp->c.huffman_black_is_zero;
} else {
line[offs] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].red;
line[offs+1] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].green;
line[offs+2] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].blue;
s_int_to_result_format(rp, 8, line + offs);
rgba[0] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].red;
rgba[1] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].green;
rgba[2] = rp->palette->color[black ^ rp->c.huffman_black_is_zero].blue;
s_int8_to_result_format(rp, rgba, line + offs);
}
}
black = !black;
@@ -859,51 +945,25 @@ static bool s_huff_find_eol(BMPREAD_R rp)
/********************************************************
* s_int_to_result_format
* convert integer values in image buffer
* s_int8_to_result_format
* convert 8-bit integer values (from indexed images)
* to selected number format.
*******************************************************/
static inline void s_int_to_result_format(BMPREAD_R rp, int frombits, unsigned char *restrict px)
static inline void s_int8_to_result_format(BMPREAD_R rp, const int *restrict fromrgba, unsigned char *restrict px)
{
int c;
uint32_t v;
if (rp->result_format == BMP_FORMAT_INT)
return;
#ifdef DEBUG
if (frombits > rp->result_bitsperchannel) {
printf("This is bad, frombits must be <= result_bitsperchannel");
exit(1);
}
#endif
for (c = rp->result_channels - 1; c >= 0; c--) {
/* going backwards because we are converting in place and
* always growing, never shrinking
*/
switch (frombits) {
case 8:
v = px[c];
break;
case 16:
v = ((uint16_t*)px)[c];
break;
case 32:
v = ((uint32_t*)px)[c];
break;
default:
#ifdef DEBUG
printf("Invalid pixel int size %d!!!", frombits);
exit(1);
#endif
break;
}
for (int c = 0; c < rp->result_channels; c++) {
switch (rp->result_format) {
case BMP_FORMAT_INT:
assert(rp->result_bitsperchannel == 8);
px[c] = fromrgba[c];
break;
case BMP_FORMAT_FLOAT:
((float*)px)[c] = (float) ((double) v / ((1ULL<<frombits)-1));
((float*)px)[c] = s_int_to_float(fromrgba[c], 8);
break;
case BMP_FORMAT_S2_13:
((uint16_t*)px)[c] = (uint16_t) ((double) v / ((1ULL<<frombits)-1) * 8192.0 + 0.5);
((uint16_t*)px)[c] = s_float_to_s2_13(s_int_to_float(fromrgba[c], 8));
break;
default:
#ifdef DEBUG

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-read-loadindexed.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -77,10 +77,15 @@ API BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette)
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (!rp->getinfo_called) {
if (rp->read_state < RS_HEADER_OK) {
logerr(rp->c.log, "Must call bmpread_load_info() before loading palette");
return BMP_RESULT_ERROR;
}
if (rp->read_state >= RS_LOAD_STARTED) {
logerr(rp->c.log, "Cannot load palette after image data");
return BMP_RESULT_ERROR;
}
if (!rp->palette) {
logerr(rp->c.log, "Image has no palette");
return BMP_RESULT_ERROR;
@@ -91,10 +96,16 @@ API BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette)
return BMP_RESULT_ERROR;
}
if (rp->result_format != BMP_FORMAT_INT) {
logerr(rp->c.log, "Palette can only be loaded when number format is BMP_FORMAT_INT");
return BMP_RESULT_ERROR;
}
memsize = rp->palette->numcolors * 4;
if (!*palette) {
if (!(*palette = malloc(memsize))) {
logsyserr(rp->c.log, "allocating palette");
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
}
@@ -103,11 +114,13 @@ API BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette)
/* irreversible. image will be returned as indexed pixels */
if (!rp->result_indexed) {
rp->result_indexed = true;
rp->dimensions_queried = false;
rp->read_state = MIN(RS_HEADER_OK, rp->read_state);
rp->dim_queried_channels = false;
rp->result_channels = 1;
if (!br_set_resultbits(rp))
if (!br_set_resultbits(rp)) {
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
}
for (i = 0; i < rp->palette->numcolors; i++) {

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-read.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -32,10 +32,10 @@
#include "bmplib.h"
#include "logging.h"
#include "bmp-common.h"
#include "bmp-read-icons.h"
#include "bmp-read.h"
const char* s_infoheader_name(int infoversion);
const char* s_compression_name(int compression);
@@ -58,6 +58,7 @@ API BMPHANDLE bmpread_new(FILE *file)
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;
@@ -103,12 +104,15 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (rp->getinfo_called)
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 */
@@ -118,10 +122,60 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
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:
logerr(rp->c.log, "Bitmap array and icon/pointer files not supported");
rp->lasterr = BMP_ERR_UNSUPPORTED;
goto abort;
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);
@@ -129,6 +183,13 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
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;
@@ -136,6 +197,11 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
/* 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;
@@ -147,11 +213,14 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
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->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)) {
@@ -192,28 +261,58 @@ API BMPRESULT bmpread_load_info(BMPHANDLE h)
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) {
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->getinfo_called = true;
rp->read_state = RS_HEADER_OK;
return rp->getinfo_return;
abort:
rp->getinfo_called = true;
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
@@ -226,6 +325,11 @@ API BMPRESULT bmpread_set_64bit_conv(BMPHANDLE h, enum Bmpconv64 conv)
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:
@@ -267,12 +371,8 @@ API int bmpread_is_64bit(BMPHANDLE h)
if (!(rp = cm_read_handle(h)))
return 0;
if (!rp->getinfo_called)
bmpread_load_info((BMPHANDLE)(void*)rp);
if (rp->getinfo_return != BMP_RESULT_OK && rp->getinfo_return != BMP_RESULT_INSANE) {
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
}
if (rp->ih->bitcount == 64)
return 1;
@@ -292,15 +392,13 @@ API size_t bmpread_iccprofile_size(BMPHANDLE h)
if (!(rp = cm_read_handle(h)))
return 0;
if (!rp->getinfo_called)
return 0;
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
if (rp->getinfo_return != BMP_RESULT_OK && rp->getinfo_return != BMP_RESULT_INSANE) {
return 0;
}
if (rp->ih->cstype == PROFILE_EMBEDDED && rp->ih->profilesize <= MAX_ICCPROFILE_SIZE)
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;
}
@@ -320,10 +418,16 @@ API BMPRESULT bmpread_load_iccprofile(BMPHANDLE h, unsigned char **profile)
if (!(rp = cm_read_handle(h)))
goto abort;
if (!rp->getinfo_called) {
logerr(rp->c.log, "Must call bmpread_load_info() before loading ICC profile");
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;
@@ -387,7 +491,7 @@ abort:
*profile = NULL;
}
if (file_messed_up)
rp->getinfo_return = BMP_RESULT_ERROR;
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
@@ -408,12 +512,11 @@ API BMPRESULT bmpread_dimensions(BMPHANDLE h, int* restrict width,
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (!rp->getinfo_called)
if (rp->read_state < RS_HEADER_OK)
bmpread_load_info((BMPHANDLE)(void*)rp);
if (rp->getinfo_return != BMP_RESULT_OK && rp->getinfo_return != BMP_RESULT_INSANE) {
return rp->getinfo_return;
}
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
if (width) {
*width = rp->width;
@@ -436,8 +539,9 @@ API BMPRESULT bmpread_dimensions(BMPHANDLE h, int* restrict width,
}
if (rp->dim_queried_width && rp->dim_queried_height &&
rp->dim_queried_channels && rp->dim_queried_bitsperchannel)
rp->dimensions_queried = true;
rp->dim_queried_channels && rp->dim_queried_bitsperchannel) {
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
}
return rp->getinfo_return;
}
@@ -456,13 +560,8 @@ BMPRESULT br_set_number_format(BMPREAD_R rp, enum BmpFormat format)
return BMP_RESULT_OK;
}
if (!(format == BMP_FORMAT_INT ||
format == BMP_FORMAT_FLOAT ||
format == BMP_FORMAT_S2_13)) {
logerr(rp->c.log, "Invalid number format (%d) specified", (int) format);
rp->lasterr = BMP_ERR_FORMAT;
if (rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
}
switch (format) {
case BMP_FORMAT_INT:
@@ -471,11 +570,16 @@ BMPRESULT br_set_number_format(BMPREAD_R rp, enum BmpFormat format)
case BMP_FORMAT_FLOAT:
case BMP_FORMAT_S2_13:
if (rp->getinfo_called && rp->result_indexed) {
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:
@@ -487,8 +591,10 @@ BMPRESULT br_set_number_format(BMPREAD_R rp, enum BmpFormat format)
rp->result_format = format;
rp->result_format_explicit = true;
if (!br_set_resultbits(rp))
if (!br_set_resultbits(rp)) {
rp->read_state = RS_FATAL;
return BMP_RESULT_ERROR;
}
return BMP_RESULT_OK;
}
@@ -551,7 +657,7 @@ static int s_single_dim_val(BMPHANDLE h, enum Dimint dim)
if (!(rp = cm_read_handle(h)))
return 0;
if (!rp->getinfo_called)
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
switch (dim) {
@@ -584,9 +690,9 @@ static int s_single_dim_val(BMPHANDLE h, enum Dimint dim)
return 0;
}
if (rp->dim_queried_width && rp->dim_queried_height &&
rp->dim_queried_channels &&
rp->dim_queried_bitsperchannel)
rp->dimensions_queried = true;
rp->dim_queried_channels && rp->dim_queried_bitsperchannel) {
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
}
return ret;
}
@@ -603,12 +709,10 @@ API size_t bmpread_buffersize(BMPHANDLE h)
if (!(rp = cm_read_handle(h)))
return 0;
if (!(rp->getinfo_called &&
(rp->getinfo_return == BMP_RESULT_OK ||
rp->getinfo_return == BMP_RESULT_INSANE)))
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
rp->dimensions_queried = true;
rp->read_state = MAX(RS_DIMENSIONS_QUERIED, rp->read_state);
return rp->result_size;
}
@@ -628,9 +732,15 @@ API void bmpread_set_insanity_limit(BMPHANDLE h, size_t limit)
rp->insanity_limit = limit;
if (rp->getinfo_return == BMP_RESULT_INSANE &&
(limit == 0 || limit >= rp->result_size)) {
rp->getinfo_return = BMP_RESULT_OK;
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;
}
}
@@ -649,6 +759,12 @@ API void bmpread_set_undefined(BMPHANDLE h, enum BmpUndefined mode)
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;
@@ -660,11 +776,6 @@ API void bmpread_set_undefined(BMPHANDLE h, enum BmpUndefined mode)
rp->undefined_mode = mode;
if (!rp->getinfo_called || (rp->getinfo_return != BMP_RESULT_OK &&
rp->getinfo_return != BMP_RESULT_INSANE)) {
return;
}
/* we are changing the setting after dimensions have */
/* been established. Only relevant for RLE-encoding */
@@ -673,7 +784,13 @@ API void bmpread_set_undefined(BMPHANDLE h, enum BmpUndefined mode)
rp->result_channels = (mode == BMP_UNDEFINED_TO_ALPHA) ? 4 : 3;
br_set_resultbits(rp);
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;
}
}
@@ -686,6 +803,20 @@ 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)
@@ -708,12 +839,40 @@ 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_HEADER;
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
@@ -747,6 +906,7 @@ static bool s_is_bmptype_supported_rgb(BMPREAD_R rp)
case BI_RGB:
/* ok */
break;
case BI_BITFIELDS:
case BI_ALPHABITFIELDS:
if (rp->ih->bitcount == 64) {
@@ -755,6 +915,7 @@ static bool s_is_bmptype_supported_rgb(BMPREAD_R rp)
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);
@@ -762,6 +923,7 @@ static bool s_is_bmptype_supported_rgb(BMPREAD_R rp)
return false;
}
break;
default:
logerr(rp->c.log, "Unsupported compression %s for RGB image",
s_compression_name(rp->ih->compression));
@@ -975,7 +1137,7 @@ bool br_set_resultbits(BMPREAD_R rp)
if (newbits != rp->result_bitsperchannel) {
rp->dim_queried_bitsperchannel = false;
rp->dimensions_queried = 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;
@@ -986,15 +1148,17 @@ bool br_set_resultbits(BMPREAD_R rp)
rp->result_size = (size_t) rp->width * rp->height * rp->result_bytes_per_pixel;
if (rp->getinfo_called) {
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;
} else {
if (rp->getinfo_return == BMP_RESULT_INSANE)
rp->getinfo_return = BMP_RESULT_OK;
}
}
return true;
}
@@ -1016,6 +1180,7 @@ static bool s_check_dimensions(BMPREAD_R rp)
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;
@@ -1280,7 +1445,7 @@ static bool s_read_file_header(BMPREAD_R rp)
/*****************************************************************************
* s_read_info_header
*****************************************************************************/
static void s_detect_os2_compression(BMPREAD_R rp);
static void s_detect_os2_header(BMPREAD_R rp);
static bool s_read_info_header(BMPREAD_R rp)
{
@@ -1294,6 +1459,11 @@ static bool s_read_info_header(BMPREAD_R rp)
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:
@@ -1403,7 +1573,7 @@ header_done:
rp->bytes_read++;
}
s_detect_os2_compression(rp);
s_detect_os2_header(rp);
return true;
@@ -1422,10 +1592,10 @@ abort_file_err:
/*****************************************************************************
* s_detect_os2_compression
* s_detect_os2_header
*****************************************************************************/
static void s_detect_os2_compression(BMPREAD_R rp)
static void s_detect_os2_header(BMPREAD_R rp)
{
if (rp->ih->version == BMPINFO_V3) {
/* might actually be a 40-byte OS/2 header */
@@ -1433,6 +1603,9 @@ static void s_detect_os2_compression(BMPREAD_R rp)
(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;
}
}
@@ -1479,7 +1652,7 @@ static int s_info_int(BMPHANDLE h, enum Infoint info)
if (!(rp = cm_read_handle(h)))
return 0;
if (!rp->getinfo_called)
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return 0;
switch (info) {
@@ -1524,12 +1697,12 @@ static const char* s_info_str(BMPHANDLE h, enum Infostr info)
if (!(rp = cm_read_handle(h)))
return "";
if (!rp->getinfo_called)
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_FATAL)
return "";
switch (info) {
case INFO_STR_HEADER_NAME:
return s_infoheader_name(rp->ih->version);
return cm_infoheader_name(rp->ih->version);
case INFO_STR_COMPRESSION_NAME:
return s_compression_name(rp->ih->compression);
@@ -1550,9 +1723,7 @@ API BMPRESULT bmpread_info_channel_bits(BMPHANDLE h, int *r, int *g, int *b, int
if (!(rp = cm_read_handle(h)))
return BMP_RESULT_ERROR;
if (!(rp->getinfo_called &&
(rp->getinfo_return == BMP_RESULT_OK ||
rp->getinfo_return == BMP_RESULT_INSANE)))
if (rp->read_state < RS_HEADER_OK || rp->read_state >= RS_ARRAY)
return BMP_RESULT_ERROR;
if (rp->ih->compression == BI_OS2_RLE24) {
@@ -1574,28 +1745,6 @@ API BMPRESULT bmpread_info_channel_bits(BMPHANDLE h, int *r, int *g, int *b, int
/*****************************************************************************
* s_infoheader_name
*****************************************************************************/
const char* s_infoheader_name(int infoversion)
{
switch (infoversion) {
case BMPINFO_CORE_OS21 : return "OS21XBITMAPHEADER";
case BMPINFO_OS22 : return "OS22XBITMAPHEADER";
case BMPINFO_V3 : return "BITMAPINFOHEADER";
case BMPINFO_V3_ADOBE1 : return "BITMAPINFOHEADER + RGB mask";
case BMPINFO_V3_ADOBE2 : return "BITMAPINFOHEADER + RGBA mask";
case BMPINFO_V4 : return "BITMAPV4HEADER";
case BMPINFO_V5 : return "BITMAPV5HEADER";
case BMPINFO_FUTURE : return "unknown future version";
default:
return "invalid infoheader version";
}
}
/*****************************************************************************
* s_compression_name
*****************************************************************************/

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-read.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-write.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -37,7 +37,7 @@
#include "huffman.h"
#include "bmp-write.h"
static void s_decide_outformat(BMPWRITE_R wp);
static bool s_decide_outformat(BMPWRITE_R wp);
static bool s_write_palette(BMPWRITE_R wp);
static bool s_write_bmp_file_header(BMPWRITE_R wp);
static bool s_write_bmp_info_header(BMPWRITE_R wp);
@@ -50,6 +50,7 @@ static int s_calc_mask_values(BMPWRITE_R wp);
static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...);
static bool s_check_already_saved(BMPWRITE_R wp);
static bool s_check_save_started(BMPWRITE_R wp);
static bool s_ready_to_save(BMPWRITE_R wp);
@@ -72,6 +73,7 @@ API BMPHANDLE bmpwrite_new(FILE *file)
wp->outorientation = BMP_ORIENT_BOTTOMUP;
wp->source_format = BMP_FORMAT_INT;
wp->huffman_fg_idx = 1;
wp->write_state = WS_INIT;
if (!(wp->c.log = logcreate()))
goto abort;
@@ -126,17 +128,21 @@ API BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
return BMP_RESULT_ERROR;
if (!(s_is_setting_compatible(wp, "srcchannels", source_channels) &&
s_is_setting_compatible(wp, "srcbits", source_bitsperchannel)))
s_is_setting_compatible(wp, "srcbits", source_bitsperchannel))) {
wp->write_state = WS_INIT;
return BMP_RESULT_ERROR;
}
if (!cm_is_one_of(3, source_bitsperchannel, 8, 16, 32)) {
logerr(wp->c.log, "Invalid number of bits per channel: %d",
(int) source_bitsperchannel);
wp->write_state = WS_INIT;
return BMP_RESULT_ERROR;
}
if (!cm_is_one_of(4, source_channels, 3, 4, 1, 2)) {
logerr(wp->c.log, "Invalid number of channels: %d", (int) source_channels);
wp->write_state = WS_INIT;
return BMP_RESULT_ERROR;
}
@@ -148,6 +154,7 @@ API BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
logerr(wp->c.log, "Invalid dimensions %ux%ux%u @ %ubits",
width, height, source_channels,
source_bitsperchannel);
wp->write_state = WS_INIT;
return BMP_RESULT_ERROR;
}
@@ -155,7 +162,7 @@ API BMPRESULT bmpwrite_set_dimensions(BMPHANDLE h,
wp->height = (int) height;
wp->source_channels = (int) source_channels;
wp->source_bitsperchannel = (int) source_bitsperchannel;
wp->dimensions_set = true;
wp->write_state = WS_DIMENSIONS_SET;
return BMP_RESULT_OK;
}
@@ -197,8 +204,10 @@ API BMPRESULT bmpwrite_set_output_bits(BMPHANDLE h, int red, int green, int blue
if (s_check_save_started(wp))
return BMP_RESULT_ERROR;
if (!s_is_setting_compatible(wp, "outbits"))
if (!s_is_setting_compatible(wp, "outbits")) {
wp->outbits_set = false;
return BMP_RESULT_ERROR;
}
if (!(cm_all_positive_int(4, red, green, blue, alpha) &&
cm_all_lessoreq_int(32, 4, red, green, blue, alpha) &&
@@ -281,7 +290,7 @@ API BMPRESULT bmpwrite_set_iccprofile(BMPHANDLE h, size_t size,
{
BMPWRITE wp;
assert(MAX_ICCPROFILE_SIZE < INT_MAX);
static_assert(MAX_ICCPROFILE_SIZE < INT_MAX);
if (!(wp = cm_write_handle(h)))
return BMP_RESULT_ERROR;
@@ -554,7 +563,7 @@ API BMPRESULT bmpwrite_set_huffman_img_fg_idx(BMPHANDLE h, int idx)
static bool s_check_already_saved(BMPWRITE_R wp)
{
if (wp->saveimage_done) {
if (wp->write_state >= WS_SAVE_DONE) {
logerr(wp->c.log, "Image already saved.");
return true;
}
@@ -568,7 +577,7 @@ static bool s_check_already_saved(BMPWRITE_R wp)
static bool s_check_save_started(BMPWRITE_R wp)
{
if (wp->saveimage_started) {
if (wp->write_state >= WS_SAVE_STARTED) {
logerr(wp->c.log, "Image save already started.");
return true;
}
@@ -637,7 +646,7 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
cm_format_name(wp->source_format));
ret = false;
}
if (wp->dimensions_set) {
if (wp->write_state >= WS_DIMENSIONS_SET) {
if (!(wp->source_channels == 1 && wp->source_bitsperchannel == 8)) {
logerr (wp->c.log, "Indexed images must be 1 channel, 8 bits");
ret = false;
@@ -647,7 +656,7 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
format = va_arg(args, enum BmpFormat);
switch (format) {
case BMP_FORMAT_FLOAT:
if (wp->dimensions_set && wp->source_bitsperchannel != 32) {
if (wp->write_state >= WS_DIMENSIONS_SET && wp->source_bitsperchannel != 32) {
logerr(wp->c.log, "float cannot be %d bits per pixel",
wp->source_bitsperchannel);
ret = false;
@@ -658,7 +667,7 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
}
break;
case BMP_FORMAT_S2_13:
if (wp->dimensions_set && wp->source_bitsperchannel != 16) {
if (wp->write_state >= WS_DIMENSIONS_SET && wp->source_bitsperchannel != 16) {
logerr(wp->c.log, "s2.13 cannot be %d bits per pixel",
wp->source_bitsperchannel);
ret = false;
@@ -674,7 +683,7 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
}
} else if (!strcmp(setting, "rle")) {
rle = va_arg(args, enum BmpRLEtype);
if (rle == BMP_RLE_AUTO || rle == BMP_RLE_RLE8) {
if (rle != BMP_RLE_NONE) {
if (wp->outorientation != BMP_ORIENT_BOTTOMUP) {
logerr(wp->c.log, "RLE is invalid with top-down BMPs");
ret = false;
@@ -694,6 +703,10 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
logerr(wp->c.log, "Indexed images cannot be 64bit");
ret = false;
}
if (wp->outbits_set) {
logerr(wp->c.log, "BMPs with specified channel bits cannot be 64bit");
ret = false;
}
} else {
logerr(wp->c.log, "Panic, invalid setting check for '%s'", setting);
ret = false;
@@ -705,63 +718,82 @@ static bool s_is_setting_compatible(BMPWRITE_R wp, const char *setting, ...)
/*****************************************************************************
* s_infoheader_size
*****************************************************************************/
static uint32_t s_infoheader_size(enum BmpInfoVer version)
{
switch(version) {
case BMPINFO_CORE_OS21: return 12;
case BMPINFO_OS22: return 64;
case BMPINFO_V3: return 40;
case BMPINFO_V3_ADOBE1: return 52;
case BMPINFO_V3_ADOBE2: return 56;
case BMPINFO_V4: return 108;
case BMPINFO_V5: return 124;
default:
return -1;
}
}
/*****************************************************************************
* s_decide_outformat
*****************************************************************************/
static void s_decide_outformat(BMPWRITE_R wp)
static bool s_decide_outformat(BMPWRITE_R wp)
{
int bitsum;
uint64_t bitmapsize, filesize, bytes_per_line;
bool needv5 = false;
int bitsum = 0;
uint64_t bitmapsize, filesize, bytes_per_line;
enum BmpInfoVer version = BMPINFO_OS22, maxversion = BMPINFO_V5;
if (wp->iccprofile || (wp->ih->intent != 0))
needv5 = true;
version = MAX(BMPINFO_V5, version);
if ((wp->source_channels == 4 || wp->source_channels == 2) &&
((wp->outbits_set && wp->cmask.bits.alpha) || !wp->outbits_set) ) {
wp->has_alpha = true;
} else {
wp->cmask.bits.alpha = 0;
wp->has_alpha = false;
}
if (wp->source_channels == 4 || wp->source_channels == 2)
wp->source_has_alpha = true;
else
wp->source_has_alpha = false;
if (!wp->outbits_set) {
if (!wp->outbits_set && !wp->palette) {
if (wp->out64bit) {
wp->cmask.bits.red = 16;
wp->cmask.bits.green = 16;
wp->cmask.bits.blue = 16;
wp->cmask.bits.alpha = 16; /* 64bit always has alpha channel */
version = MAX(BMPINFO_V3, version);
} else {
wp->cmask.bits.red = wp->cmask.bits.green = wp->cmask.bits.blue = 8;
if (wp->has_alpha)
if (wp->source_has_alpha)
wp->cmask.bits.alpha = 8;
}
}
bitsum = s_calc_mask_values(wp);
if (wp->palette) {
wp->ih->version = BMPINFO_V3;
wp->ih->size = BMPIHSIZE_V3;
if (wp->source_channels > 1) {
logerr(wp->c.log, "Panic! Palette set with %d source channels",
wp->source_channels);
return false;
}
if (wp->rle_requested != BMP_RLE_NONE) {
if (wp->palette->numcolors > 16 ||
wp->rle_requested == BMP_RLE_RLE8) {
if (wp->palette->numcolors > 16 || wp->rle_requested == BMP_RLE_RLE8) {
wp->rle = 8;
wp->ih->compression = BI_RLE8;
wp->ih->bitcount = 8;
version = MAX(BMPINFO_V3, version);
} else if (wp->palette->numcolors > 2 || !wp->allow_huffman || needv5) {
} else if (wp->palette->numcolors > 2 || !wp->allow_huffman || version > BMPINFO_OS22) {
wp->rle = 4;
wp->ih->compression = BI_RLE4;
wp->ih->bitcount = 4;
version = MAX(BMPINFO_V3, version);
} else {
wp->rle = 1;
wp->ih->compression = BI_OS2_HUFFMAN;
wp->ih->bitcount = 1;
wp->ih->version = BMPINFO_OS22;
wp->ih->size = BMPIHSIZE_OS22;
version = MAX(BMPINFO_OS22, version);
maxversion = BMPINFO_OS22;
}
} else {
@@ -774,52 +806,61 @@ static void s_decide_outformat(BMPWRITE_R wp)
}
} else if (wp->allow_rle24 && wp->source_channels == 3 && wp->source_bitsperchannel == 8 &&
wp->rle_requested == BMP_RLE_AUTO && !needv5) {
wp->rle_requested == BMP_RLE_AUTO && version <= BMPINFO_OS22) {
wp->rle = 24;
wp->ih->compression = BI_OS2_RLE24;
wp->ih->bitcount = 24;
wp->ih->version = BMPINFO_OS22;
wp->ih->size = BMPIHSIZE_OS22;
version = MAX(BMPINFO_OS22, version);
maxversion = BMPINFO_OS22;
} else if (bitsum < 64 && (!cm_all_equal_int(3, (int) wp->cmask.bits.red,
(int) wp->cmask.bits.green,
(int) wp->cmask.bits.blue) ||
wp->has_alpha ||
(wp->cmask.bits.red > 0 &&
wp->cmask.bits.red != 5 &&
wp->cmask.bits.red != 8 ) )) {
/* we need BI_BITFIELDS if any of the following is true and we are not
* writing a 64bit BMP:
* - not all RGB-components have the same bitlength
* - we are writing an alpha-channel
* - bits per component are not either 5 or 8 (which have
* known RI_RGB representation)
*/
wp->ih->version = BMPINFO_V4;
wp->ih->size = BMPIHSIZE_V4;
wp->ih->compression = BI_BITFIELDS; /* do we need BI_ALPHABITFIELDS when alpha is present */
/* or will that just confuse other readers? !!!!! */
if (bitsum <= 16)
wp->ih->bitcount = 16;
else
wp->ih->bitcount = 32;
} else {
/* otherwise, use BI_RGB with either 5 or 8 bits per component
* resulting in bitcount of 16 or 24, or a 64bit BMP with 16 bits/comp.
*/
wp->ih->version = BMPINFO_V3;
wp->ih->size = BMPIHSIZE_V3;
wp->ih->compression = BI_RGB;
wp->ih->bitcount = (bitsum + 7) / 8 * 8;
/* RGB */
bitsum = s_calc_mask_values(wp);
if (bitsum < 64 && (!cm_all_equal_int(3, (int) wp->cmask.bits.red,
(int) wp->cmask.bits.green,
(int) wp->cmask.bits.blue) ||
wp->source_has_alpha ||
(wp->cmask.bits.red > 0 &&
wp->cmask.bits.red != 5 &&
wp->cmask.bits.red != 8 ) )) {
/* we need BI_BITFIELDS if any of the following is true and we are not
* writing a 64bit BMP:
* - not all RGB-components have the same bitlength
* - we are writing an alpha-channel
* - bits per component are not either 5 or 8 (which have
* known RI_RGB representation)
*/
version = MAX(BMPINFO_V4, version);
wp->ih->compression = BI_BITFIELDS;
if (bitsum <= 16)
wp->ih->bitcount = 16;
else
wp->ih->bitcount = 32;
} else {
/* otherwise, use BI_RGB with either 5 or 8 bits per component
* resulting in bitcount of 16 or 24, or a 64bit BMP with 16 bits/comp.
*/
wp->ih->compression = BI_RGB;
wp->ih->bitcount = (bitsum + 7) / 8 * 8;
}
}
if (needv5) {
assert(wp->ih->version >= BMPINFO_V3);
wp->ih->version = BMPINFO_V5;
wp->ih->size = BMPIHSIZE_V5;
if (version > maxversion) {
logerr(wp->c.log, "Panic! Info header version conflict. Have %s, need %s",
cm_infoheader_name(version), cm_infoheader_name(maxversion));
return false;
}
/* always use at least V3, unless a smaller version is required */
version = MAX(MIN(BMPINFO_V3, maxversion), version);
wp->ih->version = version;
wp->ih->size = s_infoheader_size(version);
if (wp->palette) {
wp->ih->clrused = wp->palette->numcolors;
} else {
@@ -838,7 +879,7 @@ static void s_decide_outformat(BMPWRITE_R wp)
bitmapsize = (bytes_per_line + wp->padding) * wp->height;
filesize = bitmapsize + BMPFHSIZE + wp->ih->size + wp->palette_size + wp->iccprofile_size;
wp->fh->type = 0x4d42; /* "BM" */
wp->fh->type = BMPFILE_BM;
wp->fh->size = (uint32_t) ((wp->rle || filesize > UINT32_MAX) ? 0 : filesize);
wp->fh->offbits = BMPFHSIZE + wp->ih->size + wp->palette_size;
@@ -850,24 +891,25 @@ static void s_decide_outformat(BMPWRITE_R wp)
wp->ih->planes = 1;
wp->ih->sizeimage = (uint32_t) ((wp->rle || bitmapsize > UINT32_MAX) ? 0 : bitmapsize);
uint64_t profileoffset = (uint64_t)wp->ih->size + wp->palette_size + bitmapsize;
wp->ih->profiledata = (uint32_t) ((wp->rle || profileoffset > UINT32_MAX) ? 0 : profileoffset);
}
if (wp->iccprofile) {
uint64_t profileoffset = bitmapsize + wp->ih->size + wp->palette_size;
wp->ih->profiledata = (uint32_t) ((wp->rle || profileoffset > UINT32_MAX) ? 0 : profileoffset);
}
return true;
}
/*****************************************************************************
* bmpwrite_save_image
*****************************************************************************/
static bool s_save_line_rgb(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line_rle(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line_huff(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line(BMPWRITE_R wp, const unsigned char *line);
API BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
{
BMPWRITE wp;
size_t offs, linesize;
int y, real_y, res;
size_t linesize, real_y;
int y;
if (!(wp = cm_write_handle(h)))
return BMP_RESULT_ERROR;
@@ -875,63 +917,26 @@ API BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
if (s_check_already_saved(wp))
return BMP_RESULT_ERROR;
if (wp->line_by_line) {
logerr(wp->c.log, "Cannot switch from line-by-line to saving full image");
return BMP_RESULT_ERROR;
}
if (!s_save_header(wp))
if (s_check_save_started(wp))
return BMP_RESULT_ERROR;
wp->saveimage_started = true;
wp->saveimage_done = true;
wp->bytes_written_before_bitdata = wp->bytes_written;
if (!s_ready_to_save(wp))
return BMP_RESULT_ERROR;
linesize = (size_t) wp->width * wp->source_bytes_per_pixel;
linesize = (size_t)wp->width * wp->source_bytes_per_pixel;
for (y = 0; y < wp->height; y++) {
real_y = (wp->outorientation == BMP_ORIENT_TOPDOWN) ? y : wp->height - y - 1;
offs = (size_t) real_y * linesize;
switch (wp->rle) {
case 4:
case 8:
case 24:
res = s_save_line_rle(wp, image + offs);
break;
case 1:
res = s_save_line_huff(wp, image + offs);
break;
default:
res = s_save_line_rgb(wp, image + offs);
break;
}
if (!res) {
logerr(wp->c.log, "failed saving line %d", y);
real_y = (size_t) ((wp->outorientation == BMP_ORIENT_TOPDOWN) ? y : wp->height - y - 1);
if (!s_save_line(wp, image + real_y * linesize)) {
wp->write_state = WS_FATAL;
return BMP_RESULT_ERROR;
}
}
if (wp->rle) {
if (wp->rle > 1) {
if (EOF == s_write_one_byte(0, wp) ||
EOF == s_write_one_byte(1, wp)) {
logsyserr(wp->c.log, "Writing RLE end-of-file marker");
return BMP_RESULT_ERROR;
}
}
else {
if (!(huff_encode_rtc(wp) && huff_flush(wp))) {
logsyserr(wp->c.log, "Writing RTC end-of-file marker");
return BMP_RESULT_ERROR;
}
}
}
if (!s_finalize_file(wp))
return BMP_RESULT_ERROR;
return BMP_RESULT_OK;
}
/*****************************************************************************
* bmpwrite_save_line
*****************************************************************************/
@@ -939,7 +944,6 @@ API BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image)
API BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
{
BMPWRITE wp;
int res;
if (!(wp = cm_write_handle(h)))
return BMP_RESULT_ERROR;
@@ -947,13 +951,33 @@ API BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
if (s_check_already_saved(wp))
return BMP_RESULT_ERROR;
wp->saveimage_started = true;
if (!s_ready_to_save(wp))
return BMP_RESULT_ERROR;
if (!wp->line_by_line) { /* first line */
if (!s_save_line(wp, line))
return BMP_RESULT_ERROR;
return BMP_RESULT_OK;
}
/*****************************************************************************
* s_save_line
*****************************************************************************/
static bool s_save_line_rgb(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line_rle(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line_huff(BMPWRITE_R wp, const unsigned char *line);
static bool s_save_line(BMPWRITE_R wp, const unsigned char *line)
{
bool res;
if (wp->write_state < WS_SAVE_STARTED) {
if (!s_save_header(wp))
goto abort;
goto fatal;
wp->write_state = WS_SAVE_STARTED;
wp->bytes_written_before_bitdata = wp->bytes_written;
wp->line_by_line = true;
}
switch (wp->rle) {
@@ -971,7 +995,7 @@ API BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
}
if (!res)
goto abort;
goto fatal;
if (++wp->lbl_y >= wp->height) {
if (wp->rle) {
@@ -979,41 +1003,51 @@ API BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line)
if (EOF == s_write_one_byte(0, wp) ||
EOF == s_write_one_byte(1, wp)) {
logsyserr(wp->c.log, "Writing RLE end-of-file marker");
goto abort;
goto fatal;
}
} else {
if (!(huff_encode_rtc(wp) && huff_flush(wp))) {
logsyserr(wp->c.log, "Writing RTC end-of-file marker");
goto abort;
goto fatal;
}
}
}
wp->saveimage_done = true;
if (!s_finalize_file(wp))
goto fatal;
wp->write_state = WS_SAVE_DONE;
}
return true;
if (!s_finalize_file(wp))
goto abort;
return BMP_RESULT_OK;
abort:
wp->saveimage_done = true;
return BMP_RESULT_ERROR;
fatal:
wp->write_state = WS_FATAL;
return false;
}
/*****************************************************************************
* s_ready_to_save
*****************************************************************************/
static bool s_ready_to_save(BMPWRITE_R wp)
{
if (wp->write_state < WS_DIMENSIONS_SET) {
logerr(wp->c.log, "Must set dimensions before saving");
return false;
}
return true;
}
/*****************************************************************************
* s_save_header
*****************************************************************************/
static bool s_save_header(BMPWRITE_R wp)
{
if (!wp->dimensions_set) {
logerr(wp->c.log, "Must set dimensions before saving");
if (!s_decide_outformat(wp))
return false;
}
s_decide_outformat(wp);
if (!s_write_bmp_file_header(wp)) {
logsyserr(wp->c.log, "Writing BMP file header");
@@ -1431,7 +1465,7 @@ static int s_calc_mask_values(BMPWRITE_R wp)
* s_imgrgb_to_outbytes
*****************************************************************************/
static inline uint16_t float_to_s2_13(double d);
static inline uint16_t s_float_to_2_13(double d);
static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
const unsigned char *restrict imgpx)
@@ -1445,7 +1479,7 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
if (wp->source_channels < 3)
rgb = false; /* grayscale */
if (wp->has_alpha) {
if (wp->source_has_alpha) {
alpha_offs = rgb ? 3 : 1;
outchannels = 4;
} else
@@ -1459,14 +1493,14 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
comp[0] = imgpx[0];
comp[1] = rgb ? imgpx[1] : comp[0];
comp[2] = rgb ? imgpx[2] : comp[0];
if (wp->has_alpha)
if (wp->source_has_alpha)
comp[3] = imgpx[alpha_offs];
break;
case 16:
comp[0] = ((const uint16_t*)imgpx)[0];
comp[1] = rgb ? ((const uint16_t*)imgpx)[1] : comp[0];
comp[2] = rgb ? ((const uint16_t*)imgpx)[2] : comp[0];
if (wp->has_alpha)
if (wp->source_has_alpha)
comp[3] = ((const uint16_t*)imgpx)[alpha_offs];
break;
@@ -1474,7 +1508,7 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
comp[0] = ((const uint32_t*)imgpx)[0];
comp[1] = rgb ? ((const uint32_t*)imgpx)[1] : comp[0];
comp[2] = rgb ? ((const uint32_t*)imgpx)[2] : comp[0];
if (wp->has_alpha)
if (wp->source_has_alpha)
comp[3] = ((const uint32_t*)imgpx)[alpha_offs];
break;
@@ -1494,12 +1528,12 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
dcomp[0] = ((const float*)imgpx)[0];
dcomp[1] = rgb ? ((const float*)imgpx)[1] : dcomp[0];
dcomp[2] = rgb ? ((const float*)imgpx)[2] : dcomp[0];
if (wp->has_alpha)
if (wp->source_has_alpha)
dcomp[3] = ((const float*)imgpx)[alpha_offs];
if (wp->out64bit) {
for (i = 0; i < outchannels; i++) {
comp[i] = float_to_s2_13(dcomp[i]);
comp[i] = s_float_to_2_13(dcomp[i]);
}
} else {
for (i = 0; i < outchannels; i++) {
@@ -1517,7 +1551,7 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
comp[0] = ((const uint16_t*)imgpx)[0];
comp[1] = rgb ? ((const uint16_t*)imgpx)[1] : comp[0];
comp[2] = rgb ? ((const uint16_t*)imgpx)[2] : comp[0];
if (wp->has_alpha)
if (wp->source_has_alpha)
comp[3] = ((const uint16_t*)imgpx)[alpha_offs];
if (wp->out64bit) {
@@ -1543,7 +1577,7 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
for (i = 0, bytes = 0; i < outchannels; i++) {
bytes |= ((unsigned long long)comp[i] & wp->cmask.mask.value[i]) << wp->cmask.shift.value[i];
}
if (!wp->has_alpha && wp->out64bit)
if (!wp->source_has_alpha && wp->out64bit)
bytes |= 8192ULL << wp->cmask.shift.alpha;
return bytes;
@@ -1552,23 +1586,15 @@ static inline unsigned long long s_imgrgb_to_outbytes(BMPWRITE_R wp,
/*****************************************************************************
* float_to_s2_13
* s_float_to_2_13
* (duplicate from bmp-read-loadimage.c)
*****************************************************************************/
static inline uint16_t float_to_s2_13(double d)
static inline uint16_t s_float_to_2_13(double d)
{
uint16_t s2_13;
d = round(d * 8192.0);
if (d >= 32767.0)
s2_13 = 0x7fff; /* max positive value */
else if (d <= -32768.0)
s2_13 = 0x8000; /* min negative value */
else
s2_13 = (uint16_t) (0xffff & (int)d);
return s2_13;
d = MIN(d, 3.99987793);
d = MAX(-4.0, d);
return (uint16_t) ((int)round(d * 8192.0) & 0xffff);
}

View File

@@ -1,6 +1,6 @@
/* bmplib - bmp-write.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - bmplib.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -84,6 +84,9 @@ typedef union Bmphandle *BMPHANDLE;
* the image unless you first call
* bmpread_set_insanity_limit() to set a new
* sufficiently high limit.
*
* BMP_RESULT_ARRAY The BMP file contains an OS/2 bitmap array.
*
*/
enum Bmpresult {
BMP_RESULT_OK = 0,
@@ -93,6 +96,7 @@ enum Bmpresult {
BMP_RESULT_PNG,
BMP_RESULT_JPEG,
BMP_RESULT_ERROR,
BMP_RESULT_ARRAY
};
typedef enum Bmpresult BMPRESULT;
@@ -113,10 +117,8 @@ typedef enum Bmpresult BMPRESULT;
*/
enum Bmpconv64 {
BMP_CONV64_SRGB = 0, /* default */
BMP_CONV64_LINEAR = 1,
BMP_CONV64_16BIT_SRGB DEPR("use BMP_CONV64_SRGB instead") = 0,
BMP_CONV64_16BIT DEPR("use BMP_CONV64_LINEAR instead") = 1,
BMP_CONV64_NONE
BMP_CONV64_LINEAR,
BMP_CONV64_NONE,
};
typedef enum Bmpconv64 BMPCONV64;
@@ -131,7 +133,7 @@ typedef enum Bmpconv64 BMPCONV64;
*/
enum BmpInfoVer {
BMPINFO_CORE_OS21 = 1, /* 12 bytes */
BMPINFO_OS22, /* 16 / 40(!) / 64 bytes */
BMPINFO_OS22, /* 16 / 40(!) / up to 64 bytes */
BMPINFO_V3, /* 40 bytes */
BMPINFO_V3_ADOBE1, /* 52 bytes, unofficial */
BMPINFO_V3_ADOBE2, /* 56 bytes, unofficial */
@@ -164,8 +166,9 @@ typedef enum BmpRLEtype BMPRLETYPE;
/*
* undefined pixels in RLE images
*
* BMP_UNDEFINED_TO_ZERO set undefined pixels to 0 (= first
* entry in color table).
* BMP_UNDEFINED_LEAVE leaves image buffer at whatever pixel value it was
* initialized to. (0, i.e. first entry in color table if
* buffer was allocated by bmplib).
*
* BMP_UNDEFINED_TO_ALPHA (default) make undefined pixels
* transparent. Always adds an alpha
@@ -174,8 +177,7 @@ typedef enum BmpRLEtype BMPRLETYPE;
*/
enum BmpUndefined {
BMP_UNDEFINED_LEAVE,
BMP_UNDEFINED_TO_ZERO DEPR("use BMP_UNDEFINED_LEAVE instead") = 0,
BMP_UNDEFINED_TO_ALPHA /* default */
BMP_UNDEFINED_TO_ALPHA, /* default */
};
typedef enum BmpUndefined BMPUNDEFINED;
@@ -195,6 +197,19 @@ enum BmpOrient {
typedef enum BmpOrient BMPORIENT;
/*
* number format
*
* format of input/output RGB(A) image data. (indexed image data
* is always 8-bit integer).
*
* BMP_FORMAT_INT 8/16/32-bit integer
*
* BMP_FORMAT_FLOAT 32-bit float
*
* BMP_FORMAT_S2_13 16-bit s2.13 fixed point with range from
* -4.0 to +3.999...
*/
enum BmpFormat {
BMP_FORMAT_INT,
BMP_FORMAT_FLOAT,
@@ -212,16 +227,39 @@ enum BmpIntent {
};
typedef enum BmpIntent BMPINTENT;
enum BmpImagetype {
BMP_IMAGETYPE_NONE,
BMP_IMAGETYPE_BM = 0x4d42,
BMP_IMAGETYPE_CI = 0x4943,
BMP_IMAGETYPE_CP = 0x5043,
BMP_IMAGETYPE_IC = 0x4349,
BMP_IMAGETYPE_PT = 0x5450,
BMP_IMAGETYPE_BA = 0x4142
};
typedef enum BmpImagetype BMPIMAGETYPE;
struct BmpArrayInfo {
BMPHANDLE handle;
BMPIMAGETYPE type;
int width, height;
int ncolors; /* 0 = RGB */
int screenwidth, screenheight; /* typically 0, or 1024x768 for 'hi-res' */
};
APIDECL BMPHANDLE bmpread_new(FILE *file);
APIDECL BMPRESULT bmpread_load_info(BMPHANDLE h);
APIDECL BMPIMAGETYPE bmpread_image_type(BMPHANDLE h);
APIDECL BMPRESULT bmpread_dimensions(BMPHANDLE h,
int *width,
int *height,
int *channels,
int *bitsperchannel,
BMPORIENT *orientation);
int *width,
int *height,
int *channels,
int *bitsperchannel,
BMPORIENT *orientation);
APIDECL int bmpread_width(BMPHANDLE h);
APIDECL int bmpread_height(BMPHANDLE h);
@@ -237,11 +275,9 @@ APIDECL size_t bmpread_buffersize(BMPHANDLE h);
APIDECL BMPRESULT bmpread_load_image(BMPHANDLE h, unsigned char **buffer);
APIDECL BMPRESULT bmpread_load_line(BMPHANDLE h, unsigned char **buffer);
APIDECL int bmpread_num_palette_colors(BMPHANDLE h);
APIDECL BMPRESULT bmpread_load_palette(BMPHANDLE h, unsigned char **palette);
APIDECL void bmpread_set_undefined(BMPHANDLE h, BMPUNDEFINED mode);
APIDECL void bmpread_set_insanity_limit(BMPHANDLE h, size_t limit);
@@ -251,6 +287,11 @@ APIDECL BMPRESULT bmpread_set_64bit_conv(BMPHANDLE h, BMPCONV64 conv);
APIDECL size_t bmpread_iccprofile_size(BMPHANDLE h);
APIDECL BMPRESULT bmpread_load_iccprofile(BMPHANDLE h, unsigned char **profile);
APIDECL int bmpread_array_num(BMPHANDLE h);
APIDECL BMPRESULT bmpread_array_info(BMPHANDLE h, struct BmpArrayInfo *ai, int idx);
APIDECL BMPINFOVER bmpread_info_header_version(BMPHANDLE h);
APIDECL const char* bmpread_info_header_name(BMPHANDLE h);
APIDECL int bmpread_info_header_size(BMPHANDLE h);
@@ -288,6 +329,7 @@ APIDECL BMPRESULT bmpwrite_save_image(BMPHANDLE h, const unsigned char *image);
APIDECL BMPRESULT bmpwrite_save_line(BMPHANDLE h, const unsigned char *line);
APIDECL BMPRESULT bmp_set_number_format(BMPHANDLE h, BMPFORMAT format);
APIDECL BMPRESULT bmp_set_huffman_t4black_value(BMPHANDLE h, int blackidx);
@@ -328,15 +370,6 @@ APIDECL const char* bmp_version(void);
/* these functions are kept for binary compatibility and will be
* removed from future versions:
*/
APIDECL int DEPR("use bmpread_orientation() instead") bmpread_topdown(BMPHANDLE h);
APIDECL void DEPR("use bmpread_set_undefined() instead") bmpread_set_undefined_to_alpha(BMPHANDLE h, int mode);
APIDECL int DEPR("use bmpread_bitsperchannel() instead") bmpread_bits_per_channel(BMPHANDLE h);
#ifdef __cplusplus
}
#endif

View File

@@ -1,6 +1,6 @@
/* bmplib - gen-huffman-codes.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - gen-huffman.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify
@@ -26,7 +26,7 @@
#include <string.h>
#if (! __bool_true_false_are_defined)
#if (! __bool_true_false_are_defined && __STDC_VERSION__ < 202000L)
typedef int bool;
#define true 1
#define false 0

View File

@@ -1,6 +1,6 @@
/* bmplib - huffman.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - huffman.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - logging.c
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,6 @@
/* bmplib - logging.h
*
* Copyright (c) 2024, Rupert Weber.
* Copyright (c) 2024, 2025, Rupert Weber.
*
* This file is part of bmplib.
* bmplib is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
project('bmplib', 'c', default_options: ['c_std=c11', 'warning_level=3'], version: '1.7.6')
project('bmplib', 'c', default_options: ['c_std=c11', 'warning_level=3'], version: '1.8.0')
cc = meson.get_compiler('c')
@@ -37,6 +37,7 @@ bmplib_sources = ['bmp-read.c',
'bmp-write.c',
'bmp-read-loadimage.c',
'bmp-read-loadindexed.c',
'bmp-read-icons.c',
'bmp-common.c',
'huffman.c',
'logging.c']
@@ -69,3 +70,64 @@ pkg_mod.generate(libraries: bmplib,
filebase: 'libbmp',
description: 'Library for reading/writing Windows BMP files.',
)
test_read_conversions = executable('test_read_conversions',
'test-read-conversions.c',
'bmp-common.c',
'bmp-read.c',
'bmp-read-icons.c',
'bmp-write.c',
'huffman.c',
'logging.c',
huff_codes[0],
dependencies: m_dep,
)
test_read_io = executable('test_read_io',
'test-read-io.c',
'bmp-common.c',
'bmp-read.c',
'bmp-read-icons.c',
'bmp-write.c',
'huffman.c',
'logging.c',
'test-fileio-pipes.c',
huff_codes[0],
dependencies: m_dep,
)
test_write_io = executable('test_write_io',
'test-write-io.c',
'bmp-common.c',
'bmp-read.c',
'bmp-read-icons.c',
'bmp-read-loadimage.c',
'huffman.c',
'logging.c',
'test-fileio-pipes.c',
huff_codes[0],
dependencies: m_dep,
)
test('read - s_s2_13_to_float', test_read_conversions, args : ['s_s2_13_to_float'])
test('read - s_convert64', test_read_conversions, args : ['s_convert64'])
test('read - s_float_to_s2_13', test_read_conversions, args : ['s_float_to_s2_13'])
test('read - roundtrip_s2.13-float-s2.13', test_read_conversions, args : ['roundtrip_s2.13-float-s2.13'])
test('read - s_srgb_gamma_float', test_read_conversions, args : ['s_srgb_gamma_float'])
test('read - s_int8_to_result_format/float', test_read_conversions, args : ['s_int8_to_result_format', 'float'])
test('read - s_int8_to_result_format/int', test_read_conversions, args : ['s_int8_to_result_format', 'int'])
test('read - s_int8_to_result_format/s2.13', test_read_conversions, args : ['s_int8_to_result_format', 's2.13'])
test('read - s_buffer32_fill', test_read_io, args : ['s_buffer32_fill'])
test('read - s_buffer32_bits', test_read_io, args : ['s_buffer32_bits'])
test('read - s_read_rgb_pixel', test_read_io, args : ['s_read_rgb_pixel'])
test('read - read_u16_le', test_read_io, args : ['read_u16_le'])
test('read - read_s16_le', test_read_io, args : ['read_s16_le'])
test('read - read_u32_le', test_read_io, args : ['read_u32_le'])
test('read - read_s32_le', test_read_io, args : ['read_s32_le'])
test('write - write_u32_le', test_write_io, args : ['write_u32_le'])
test('write - write_s32_le', test_write_io, args : ['write_s32_le'])
test('write - write_u16_le', test_write_io, args : ['write_u16_le'])
test('write - write_s16_le', test_write_io, args : ['write_s16_le'])
test('write - s_imgrgb_to_outbytes/int', test_write_io, args : ['s_imgrgb_to_outbytes', 'int'])
test('write - s_imgrgb_to_outbytes/float', test_write_io, args : ['s_imgrgb_to_outbytes', 'float'])
test('write - s_imgrgb_to_outbytes/s2.13', test_write_io, args : ['s_imgrgb_to_outbytes', 's2.13'])

145
test-fileio-pipes.c Normal file
View File

@@ -0,0 +1,145 @@
/* bmplib - test-fileio-pipes.c
*
* Copyright (c) 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/>
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <test-fileio-pipes.h>
FILE* provide_as_file(const unsigned char *data, size_t size)
{
int fd[2] = { -1, -1 };
FILE *file_read = NULL, *file_write = NULL;
if (pipe(fd)) {
perror("pipe");
goto abort;
}
if (!(file_read = fdopen(fd[0], "rb"))) {
perror("fdopen read pipe");
goto abort;
}
if (!(file_write = fdopen(fd[1], "w"))) {
perror("fdopen write pipe");
goto abort;
}
if (size != fwrite(data, 1, size, file_write)) {
perror("fwrite to pipe");
goto abort;
}
fclose(file_write);
return file_read;
abort:
if (file_write)
fclose(file_write);
else if (fd[1] != -1)
close(fd[1]);
if (file_read)
fclose(file_read);
else if (fd[0] != -1)
close(fd[0]);
return NULL;
}
FILE* open_write_pipe(struct WritePipe **hwp)
{
int fd[2] = { -1, -1 };
FILE *file_read = NULL, *file_write = NULL;
struct WritePipe *wp = NULL;
if (!hwp)
return NULL;
*hwp = NULL;
if (!(wp = malloc(sizeof *wp))) {
perror(__func__);
goto abort;
}
memset(wp, 0, sizeof *wp);
if (pipe(fd)) {
perror("pipe");
goto abort;
}
if (!(file_read = fdopen(fd[0], "rb"))) {
perror("fdopen read pipe");
goto abort;
}
if (!(file_write = fdopen(fd[1], "w"))) {
perror("fdopen write pipe");
goto abort;
}
wp->file_read = file_read;
*hwp = wp;
return file_write;
abort:
if (file_write)
fclose(file_write);
else if (fd[1] != -1)
close(fd[1]);
if (file_read)
fclose(file_read);
else if (fd[0] != -1)
close(fd[0]);
if (wp)
free(wp);
return NULL;
}
int data_from_write_pipe(struct WritePipe *wp, unsigned char *buffer, int size)
{
int nread;
if (!(wp && buffer)) {
fprintf(stderr, "%s(): invalid NULL argument(s)\n", __func__);
exit(3);
}
if (!wp->file_read) {
fprintf(stderr, "%s(): FILE* is NULL\n", __func__);
exit(3);
}
nread = fread(buffer, 1, size, wp->file_read);
fclose(wp->file_read);
free(wp);
return nread;
}

29
test-fileio-pipes.h Normal file
View File

@@ -0,0 +1,29 @@
/* bmplib - test-fileio-pipes.h
*
* Copyright (c) 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/>
*/
struct WritePipe {
FILE *file_read;
};
FILE* provide_as_file(const unsigned char *data, size_t size);
FILE* open_write_pipe(struct WritePipe **hwp);
int data_from_write_pipe(struct WritePipe *wp, unsigned char *buffer, int size);

247
test-read-conversions.c Normal file
View File

@@ -0,0 +1,247 @@
/* bmplib - test-read-conversions.c
*
* Copyright (c) 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 "bmp-read-loadimage.c"
#define ARRAY_LEN(a) ((int)(sizeof (a) / sizeof ((a)[0])))
int main(int argc, const char **argv)
{
const char *func, *subtest ="";
if (argc < 2) {
fprintf(stderr, "Invalid invocation\n");
return 2;
}
func = argv[1];
if (argc >= 3)
subtest = argv[2];
if (!strcmp(func, "s_float_to_s2_13")) {
struct {
double d;
uint16_t expected;
} data[] = {
{ .d = -4.0, .expected = 0x8000 },
{ .d = -5.0, .expected = 0x8000 },
{ .d = -1.0, .expected = 0xe000 },
{ .d = 0.0, .expected = 0x0000 },
{ .d = 1.0, .expected = 0x2000 },
{ .d = 3.99987793, .expected = 0x7fff },
{ .d = 4.0, .expected = 0x7fff },
{ .d = 20.0, .expected = 0x7fff },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
uint16_t s2_13 = s_float_to_s2_13(data[i].d);
if (s2_13 != data[i].expected) {
printf("%s() failed on data set %d: %f\n", func, i, data[i].d);
printf("expected 0x%04x, got 0x%04x\n", data[i].expected, s2_13);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_s2_13_to_float")) {
struct {
uint16_t s2_13;
double expected;
} data[] = {
{ .s2_13 = 0x2000u, .expected = 1.0},
{ .s2_13 = 0xe000u, .expected = -1.0},
{ .s2_13 = 0, .expected = 0.0},
{ .s2_13 = 0x7fffu, .expected = 3.99987793},
{ .s2_13 = 0x8000u, .expected = -4.0},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
double d = s_s2_13_to_float(data[i].s2_13);
printf("is %.12f, expected %.12f\n", d, data[i].expected);
if (fabs(d - data[i].expected) > 0.000000001) {
printf("%s() failed on data set %d: 0x%04x\n", func, i, data[i].s2_13);
printf("expected %.12f, got %.12f\n", data[i].expected, d);
return 1;
}
}
return 0;
}
if (!strcmp(func, "roundtrip_s2.13-float-s2.13")) {
for (uint32_t u = 0; u <= 0xffffu; u++) {
double d = s_s2_13_to_float(u);
uint16_t u16 = s_float_to_s2_13(d);
if (u != u16) {
printf("%s for 0x%04x failed:\n", func, (unsigned)u);
printf("expected 0x%04x, got 0x%04x\n", (unsigned)u, (unsigned)u16);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_convert64")) {
struct {
uint16_t val64[4];
uint16_t expected[4];
} data[] = {
{ .val64 = {0, 0, 0, 0}, .expected = {0, 0, 0, 0} },
{ .val64 = {0x2000, 0xe000, 0x2000, 0xffff}, .expected = {0xffff, 0, 0xffff, 0} },
{ .val64 = {0x4000, 0x1000, 0x4000, 0x1000}, .expected = {0xffff, 0x8000, 0xffff, 0x8000} },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
s_convert64(data[i].val64);
for (int j = 0; j < 4; j++) {
if (data[i].val64[j] != data[i].expected[j]) {
printf("%s() failed on data set %d\n", func, i);
printf("expected 0x%04x, got 0x%04x\n", (unsigned)data[i].expected[j],
(unsigned)data[i].val64[j]);
return 1;
}
}
}
return 0;
}
if (!strcmp(func, "s_srgb_gamma_float")) {
struct {
double lin;
double expected;
} data[] = {
{ .lin = 0.0, .expected = 0.0},
{ .lin = 1.0, .expected = 1.0},
{ .lin = 0.1, .expected = 0.349190213},
{ .lin = 0.5, .expected = 0.735356983},
{ .lin = 0.9, .expected = 0.954687172},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
double d = s_srgb_gamma_float(data[i].lin);
printf("is %.12f, expected %.12f\n", d, data[i].expected);
if (fabs(d - data[i].expected) > 0.000000001) {
printf("%s() failed on data set %d: %f\n", func, i, data[i].lin);
printf("expected %.12f, got %.12f\n", data[i].expected, d);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_int8_to_result_format") && !strcmp(subtest, "float")) {
struct Bmpread br = { .result_format = BMP_FORMAT_FLOAT, .result_bitsperchannel = 32 };
struct {
int channels;
int rgba[4];
float expected[4];
} data[] = {
{ .channels = 3, .rgba = { 0, 0, 0, 0}, .expected = { 0.0, 0.0, 0.0, 0.0 } },
{ .channels = 3, .rgba = { 255, 255, 255, 255}, .expected = { 1.0, 1.0, 1.0, 0.0 } },
{ .channels = 4, .rgba = { 127, 128, 1, 254}, .expected = { 0.4980392, 0.5019608, 0.0039216, 0.9960784 } },
};
float floatbuf[4] = { 0 };
for (int i = 0; i < ARRAY_LEN(data); i++) {
br.result_channels = data[i].channels;
s_int8_to_result_format(&br, data[i].rgba, (unsigned char*) floatbuf);
//printf("is %.12f, expected %.12f\n", d, data[i].expected);
for (int j = 0; j < 4; j++) {
if (fabs(floatbuf[j] - data[i].expected[j]) > 0.0000001) {
printf("%s()/%s - failed on data set %d:\n", func, subtest, i);
printf("expected %.12f, got %.12f\n", data[i].expected[j], floatbuf[j]);
return 1;
}
}
}
return 0;
}
if (!strcmp(func, "s_int8_to_result_format") && !strcmp(subtest, "int")) {
struct Bmpread br = { .result_format = BMP_FORMAT_INT, .result_bitsperchannel = 8 };
struct {
int channels;
int rgba[4];
unsigned expected[4];
} data[] = {
{ .channels = 3, .rgba = { 0, 0, 0, 0}, .expected = { 0, 0, 0, 0 } },
{ .channels = 3, .rgba = { 255, 255, 255, 255}, .expected = { 255, 255, 255, 0 } },
{ .channels = 4, .rgba = { 127, 128, 1, 254}, .expected = { 127, 128, 1, 254 } },
};
uint8_t int8buf[4] = { 0 };
for (int i = 0; i < ARRAY_LEN(data); i++) {
br.result_channels = data[i].channels;
s_int8_to_result_format(&br, data[i].rgba, (unsigned char*) int8buf);
for (int j = 0; j < 4; j++) {
if (int8buf[j] != data[i].expected[j]) {
printf("%s()/%s - failed on data set %d:\n", func, subtest, i);
printf("expected %u, got %u\n", (unsigned)data[i].expected[j], (unsigned)int8buf[j]);
return 1;
}
}
}
return 0;
}
if (!strcmp(func, "s_int8_to_result_format") && !strcmp(subtest, "s2.13")) {
struct Bmpread br = { .result_format = BMP_FORMAT_S2_13, .result_bitsperchannel = 16 };
struct {
int channels;
int rgba[4];
uint16_t expected[4];
} data[] = {
{ .channels = 3, .rgba = { 0, 0, 0, 0}, .expected = { 0, 0, 0, 0 } },
{ .channels = 3, .rgba = { 255, 255, 255, 255}, .expected = { 0x2000, 0x2000, 0x2000, 0 } },
{ .channels = 4, .rgba = { 127, 128, 1, 254}, .expected = { 0x0ff0, 0x1010, 0x0020, 0x1fe0 } },
};
uint16_t s213buf[4] = { 0 };
for (int i = 0; i < ARRAY_LEN(data); i++) {
br.result_channels = data[i].channels;
s_int8_to_result_format(&br, data[i].rgba, (unsigned char*) s213buf);
for (int j = 0; j < 4; j++) {
if (s213buf[j] != data[i].expected[j]) {
printf("%s()/%s - failed on data set %d:\n", func, subtest, i);
printf("expected %u, got %u\n", (unsigned)data[i].expected[j], (unsigned)s213buf[j]);
return 1;
}
}
}
return 0;
}
fprintf(stderr, "Invalid test '%s'\n", func);
return 2;
}

377
test-read-io.c Normal file
View File

@@ -0,0 +1,377 @@
/* bmplib - test-read-io.c
*
* Copyright (c) 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 "bmp-read-loadimage.c"
#include "test-fileio-pipes.h"
#define ARRAY_LEN(a) ((int)(sizeof (a) / sizeof ((a)[0])))
int main(int argc, const char **argv)
{
const char *func /*, *subtest =""*/;
if (argc < 2) {
fprintf(stderr, "Invalid invocation\n");
return 2;
}
func = argv[1];
/*if (argc >= 3)
subtest = argv[2];*/
if (!strcmp(func, "s_buffer32_fill")) {
struct Bmpread br = { 0 };
struct Buffer32 buf32;
struct {
unsigned char *filedata;
size_t datalen;
uint32_t expected_data;
int expected_n;
bool expected_return;
} data[] = {
{ .filedata = (unsigned char[]){0x00,0x00,0x00,0x00},
.datalen = 4,
.expected_n = 32,
.expected_data = 0,
.expected_return = true },
{ .filedata = (unsigned char[]){0x01,0x02,0x03,0x04},
.datalen = 4,
.expected_n = 32,
.expected_data = 0x01020304UL,
.expected_return = true },
{ .filedata = (unsigned char[]){0x01,0x02,0x03},
.datalen = 3,
.expected_n = 24,
.expected_data = 0x01020300UL,
.expected_return = true },
{ .filedata = (unsigned char[]){ 0 },
.datalen = 0,
.expected_n = 0,
.expected_data = 0x0UL,
.expected_return = false },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
br.file = provide_as_file(data[i].filedata, data[i].datalen);
if (!br.file)
return 2;
bool result = s_buffer32_fill(&br, &buf32);
if (result != data[i].expected_return || buf32.buffer != data[i].expected_data ||
buf32.n != data[i].expected_n) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected %s/0x%08lx/%d, got %s/0x%08lx/%d\n", data[i].expected_return ? "true" : "false",
(unsigned long)data[i].expected_data,
data[i].expected_n,
result ? "true" : "false",
(unsigned long)buf32.buffer, buf32.n);
return 1;
fclose(br.file);
}
}
return 0;
}
if (!strcmp(func, "s_buffer32_bits")) {
struct Buffer32 buf32 = { 0 };
struct {
uint32_t buffer;
int n;
int request;
uint32_t expected;
} data[] = {
{ .buffer = 0x0f000000UL, .n = 8, .request = 8, .expected = 0x0f },
{ .buffer = 0x34ffffffUL, .n = 16, .request = 4, .expected = 0x03 },
{ .buffer = 0x1234ffffUL, .n = 32, .request = 8, .expected = 0x12 },
{ .buffer = 0x8234ffffUL, .n = 16, .request = 2, .expected = 0x02 },
{ .buffer = 0x8234ffffUL, .n = 16, .request = 1, .expected = 0x01 },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
buf32.buffer = data[i].buffer;
buf32.n = data[i].n;
uint32_t result = s_buffer32_bits(&buf32, data[i].request);
if (result != data[i].expected) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected 0x%08lx, got 0x%08lx\n",
(unsigned long)data[i].expected,
(unsigned long)result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_read_rgb_pixel")) {
struct Bmpread br = { 0 };
struct Bmpinfo ih = { 0 };
union Pixel px = { 0 };
br.ih = &ih;
struct {
unsigned char *filedata;
size_t datalen;
int bitcount;
uint64_t mask[4];
int shift[4];
uint32_t expected[4];
bool has_alpha;
int result_bitsperchannel;
} data[] = {
{ .filedata = (unsigned char[]){0x03,0x02,0x01,0x00},
.datalen = 4,
.bitcount = 32,
.mask = { 0xff0000, 0xff00, 0xff, 0 },
.shift = { 16, 8, 0, 0 },
.has_alpha = false,
.expected = { 1, 2, 3, 255 },
.result_bitsperchannel = 8,
},
{ .filedata = (unsigned char[]){0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08},
.datalen = 8,
.bitcount = 64,
.mask = { 0xffffULL, 0xffff0000ULL, 0xffff00000000ULL, 0xffff000000000000ULL },
.shift = { 0, 16, 32, 48 },
.has_alpha = true,
.expected = { 0x0201, 0x0403, 0x0605, 0x0807 },
.result_bitsperchannel = 16,
},
{ .filedata = (unsigned char[]){ 0x12, 0x34 },
.datalen = 2,
.bitcount = 16,
.mask = { 0x0f, 0xf0, 0x0f00, 0xf000 },
.shift = { 0, 4, 8, 12 },
.has_alpha = true,
.expected = { 2, 1, 4, 3 },
.result_bitsperchannel = 8,
},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
br.file = provide_as_file(data[i].filedata, data[i].datalen);
if (!br.file)
return 2;
br.cmask.mask.red = data[i].mask[0];
br.cmask.mask.green = data[i].mask[1];
br.cmask.mask.blue = data[i].mask[2];
br.cmask.mask.alpha = data[i].mask[3];
br.cmask.shift.red = data[i].shift[0];
br.cmask.shift.green = data[i].shift[1];
br.cmask.shift.blue = data[i].shift[2];
br.cmask.shift.alpha = data[i].shift[3];
br.has_alpha = data[i].has_alpha;
br.result_bitsperchannel = data[i].result_bitsperchannel;
ih.bitcount = data[i].bitcount;
if (!s_read_rgb_pixel(&br, &px)) {
printf("%s(): EOF or file error (.filedata too short?)\n", func);
return 2;
}
for (int j = 0; j < 4; j++) {
if (px.value[j] != data[i].expected[j]) {
printf("%s() failed on dataset %d, val %d:\n", func, i, j);
printf("expected %d, got %d\n",
(int)data[i].expected[j],
(int)px.value[j]);
return 1;
}
}
fclose(br.file);
br.file = NULL;
}
return 0;
}
if (!strcmp(func, "read_u16_le")) {
struct {
unsigned char *filedata;
int datalen;
unsigned expected;
} data[] = {
{ .filedata = (unsigned char[]){0x00,0x00},
.datalen = 2, .expected = 0 },
{ .filedata = (unsigned char[]){0x01,0x00},
.datalen = 2, .expected = 1 },
{ .filedata = (unsigned char[]){0xfe,0xff},
.datalen = 2, .expected = 65534 },
{ .filedata = (unsigned char[]){0xff,0xff},
.datalen = 2, .expected = 65535 },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
FILE *file = provide_as_file(data[i].filedata, data[i].datalen);
if (!file)
return 2;
uint16_t result = 0;
if (!read_u16_le(file, &result)) {
perror(func);
return 3;
}
if (result != data[i].expected) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected 0x%04x, got 0x%04x\n",
(unsigned)data[i].expected,
(unsigned)result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "read_s16_le")) {
struct {
unsigned char *filedata;
int datalen;
int expected;
} data[] = {
{ .filedata = (unsigned char[]){0x00,0x00},
.datalen = 2, .expected = 0 },
{ .filedata = (unsigned char[]){0x01,0x00},
.datalen = 2, .expected = 1 },
{ .filedata = (unsigned char[]){0xff,0xff},
.datalen = 2, .expected = -1 },
{ .filedata = (unsigned char[]){0x00,0x80},
.datalen = 2, .expected = -32768 },
{ .filedata = (unsigned char[]){0x01,0x80},
.datalen = 2, .expected = -32767 },
{ .filedata = (unsigned char[]){0xfe,0x7f},
.datalen = 2, .expected = 32766 },
{ .filedata = (unsigned char[]){0xff,0x7f},
.datalen = 2, .expected = 32767 },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
FILE *file = provide_as_file(data[i].filedata, data[i].datalen);
if (!file)
return 2;
int16_t result = 0;
if (!read_s16_le(file, &result)) {
perror(func);
return 3;
}
if (result != data[i].expected) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected %d, got %d\n", (int)data[i].expected,
(int)result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "read_u32_le")) {
struct {
unsigned char *filedata;
int datalen;
uint32_t expected;
} data[] = {
{ .filedata = (unsigned char[]){0x00,0x00,0x00,0x00},
.datalen = 4, .expected = 0 },
{ .filedata = (unsigned char[]){0x01,0x00,0x00,0x00},
.datalen = 4, .expected = 1 },
{ .filedata = (unsigned char[]){0xfe,0xff,0xff,0xff},
.datalen = 4, .expected = 0xfffffffeUL },
{ .filedata = (unsigned char[]){0xff,0xff,0xff,0xff},
.datalen = 4, .expected = 0xffffffffUL },
{ .filedata = (unsigned char[]){0x12,0x34,0x56,0x78},
.datalen = 4, .expected = 0x78563412UL },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
FILE *file = provide_as_file(data[i].filedata, data[i].datalen);
if (!file)
return 2;
uint32_t result = 0;
if (!read_u32_le(file, &result)) {
perror(func);
return 3;
}
if (result != data[i].expected) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected 0x%08lx, got 0x%08lx\n",
(unsigned long)data[i].expected,
(unsigned long)result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "read_s32_le")) {
struct {
unsigned char *filedata;
int datalen;
long expected;
} data[] = {
{ .filedata = (unsigned char[]){0x00,0x00,0x00,0x00},
.datalen = 4, .expected = 0 },
{ .filedata = (unsigned char[]){0x01,0x00,0x00,0x00},
.datalen = 4, .expected = 1 },
{ .filedata = (unsigned char[]){0xff,0xff,0xff,0xff},
.datalen = 4, .expected = -1 },
{ .filedata = (unsigned char[]){0x00,0x00,0x00,0x80},
.datalen = 4, .expected = -2147483648L },
{ .filedata = (unsigned char[]){0x01,0x00,0x00,0x80},
.datalen = 4, .expected = -2147483647L },
{ .filedata = (unsigned char[]){0xfe,0xff,0xff,0x7f},
.datalen = 4, .expected = 2147483646L },
{ .filedata = (unsigned char[]){0xff,0xff,0xff,0x7f},
.datalen = 4, .expected = 2147483647L },
{ .filedata = (unsigned char[]){0x12,0x34,0x56,0x78},
.datalen = 4, .expected = 2018915346L },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
FILE *file = provide_as_file(data[i].filedata, data[i].datalen);
if (!file)
return 2;
int32_t result = 0;
if (!read_s32_le(file, &result)) {
perror(func);
return 3;
}
if (result != data[i].expected) {
printf("%s() failed on dataset %d:\n", func, i);
printf("expected %ld, got %ld\n", (long)data[i].expected,
(long)result);
return 1;
}
}
return 0;
}
fprintf(stderr, "Invalid test '%s'\n", func);
return 2;
}

BIN
test-write-helper.ods Normal file

Binary file not shown.

431
test-write-io.c Normal file
View File

@@ -0,0 +1,431 @@
/* bmplib - test-read-io.c
*
* Copyright (c) 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 "bmp-write.c"
#include "test-fileio-pipes.h"
#define ARRAY_LEN(a) ((int)(sizeof (a) / sizeof ((a)[0])))
int main(int argc, const char **argv)
{
const char *func, *subtest ="";
if (argc < 2) {
fprintf(stderr, "Invalid invocation\n");
return 2;
}
func = argv[1];
if (argc >= 3)
subtest = argv[2];
if (!strcmp(func, "write_u32_le")) {
const int expected_len = 4;
struct {
uint32_t value;
unsigned char *expected;
} data[] = {
{ .expected = (unsigned char[]){0x00,0x00,0x00,0x00}, .value = 0 },
{ .expected = (unsigned char[]){0x01,0x00,0x00,0x00}, .value = 1 },
{ .expected = (unsigned char[]){0xfe,0xff,0xff,0xff}, .value = 0xfffffffeUL },
{ .expected = (unsigned char[]){0xff,0xff,0xff,0xff}, .value = 0xffffffffUL },
{ .expected = (unsigned char[]){0x12,0x34,0x56,0x78}, .value = 0x78563412UL },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
struct WritePipe *wp = NULL;
unsigned char buf[expected_len];
FILE *file = open_write_pipe(&wp);
if (!file)
return 2;
if (!write_u32_le(file, data[i].value)) {
perror(func);
return 3;
}
fflush(file); /* important, otherwise read will get stuck */
int nbytes = data_from_write_pipe(wp, buf, (int) sizeof buf);
fclose(file);
if (nbytes != expected_len) {
printf("%s() failed on dataset %d (%lu):\n", func, i, (unsigned long)data[i].value);
printf("expected to read %d bytes, got %d bytes\n", expected_len, nbytes);
return 1;
}
if (memcmp(data[i].expected, buf, expected_len) != 0) {
printf("%s() failed on dataset %d (%lu):\n", func, i, (unsigned long)data[i].value);
printf("expected 0x%02x%02x%02x%02x, got 0x%02x%02x%02x%02x\n",
(unsigned)data[i].expected[0], (unsigned)data[i].expected[1],
(unsigned)data[i].expected[2], (unsigned)data[i].expected[3],
(unsigned)buf[0], (unsigned)buf[1], (unsigned)buf[2], (unsigned)buf[3]);
return 1;
}
}
return 0;
}
if (!strcmp(func, "write_s32_le")) {
const int expected_len = 4;
struct {
int32_t value;
unsigned char *expected;
} data[] = {
{ .expected = (unsigned char[]){0x00,0x00,0x00,0x00}, .value = 0 },
{ .expected = (unsigned char[]){0x01,0x00,0x00,0x00}, .value = 1 },
{ .expected = (unsigned char[]){0xff,0xff,0xff,0xff}, .value = -1 },
{ .expected = (unsigned char[]){0x00,0x00,0x00,0x80}, .value = -2147483648L },
{ .expected = (unsigned char[]){0x01,0x00,0x00,0x80}, .value = -2147483647L },
{ .expected = (unsigned char[]){0xfe,0xff,0xff,0x7f}, .value = 2147483646L },
{ .expected = (unsigned char[]){0xff,0xff,0xff,0x7f}, .value = 2147483647L },
{ .expected = (unsigned char[]){0x12,0x34,0x56,0x78}, .value = 2018915346L },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
struct WritePipe *wp = NULL;
unsigned char buf[expected_len];
FILE *file = open_write_pipe(&wp);
if (!file)
return 2;
if (!write_s32_le(file, data[i].value)) {
perror(func);
return 3;
}
fflush(file); /* important, otherwise read will get stuck */
int nbytes = data_from_write_pipe(wp, buf, (int) sizeof buf);
fclose(file);
if (nbytes != expected_len) {
printf("%s() failed on dataset %d (%ld):\n", func, i, (long)data[i].value);
printf("expected to read %d bytes, got %d bytes\n", expected_len, nbytes);
return 1;
}
if (memcmp(data[i].expected, buf, expected_len) != 0) {
printf("%s() failed on dataset %d (%ld):\n", func, i, (long)data[i].value);
printf("expected 0x%02x%02x%02x%02x, got 0x%02x%02x%02x%02x\n",
(unsigned)data[i].expected[0], (unsigned)data[i].expected[1],
(unsigned)data[i].expected[2], (unsigned)data[i].expected[3],
(unsigned)buf[0], (unsigned)buf[1], (unsigned)buf[2], (unsigned)buf[3]);
return 1;
}
}
return 0;
}
if (!strcmp(func, "write_u16_le")) {
const int expected_len = 2;
struct {
uint16_t value;
unsigned char *expected;
} data[] = {
{ .expected = (unsigned char[]){0x00,0x00}, .value = 0 },
{ .expected = (unsigned char[]){0x01,0x00}, .value = 1 },
{ .expected = (unsigned char[]){0xfe,0xff}, .value = 65534 },
{ .expected = (unsigned char[]){0xff,0xff}, .value = 65535 },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
struct WritePipe *wp = NULL;
unsigned char buf[expected_len];
FILE *file = open_write_pipe(&wp);
if (!file)
return 2;
if (!write_u16_le(file, data[i].value)) {
perror(func);
return 3;
}
fflush(file); /* important, otherwise read will get stuck */
int nbytes = data_from_write_pipe(wp, buf, (int) sizeof buf);
fclose(file);
if (nbytes != expected_len) {
printf("%s() failed on dataset %d (%u):\n", func, i, (unsigned)data[i].value);
printf("expected to read %d bytes, got %d bytes\n", expected_len, nbytes);
return 1;
}
if (memcmp(data[i].expected, buf, expected_len) != 0) {
printf("%s() failed on dataset %d (%u):\n", func, i, (unsigned)data[i].value);
printf("expected 0x%02x%02x, got 0x%02x%02x\n",
(unsigned)data[i].expected[0], (unsigned)data[i].expected[1],
(unsigned)buf[0], (unsigned)buf[1]);
return 1;
}
}
return 0;
}
if (!strcmp(func, "write_s16_le")) {
const int expected_len = 2;
struct {
int16_t value;
unsigned char *expected;
} data[] = {
{ .expected = (unsigned char[]){0x00,0x00}, .value = 0 },
{ .expected = (unsigned char[]){0x01,0x00}, .value = 1 },
{ .expected = (unsigned char[]){0x00,0x80}, .value = -32768 },
{ .expected = (unsigned char[]){0x01,0x80}, .value = -32767 },
{ .expected = (unsigned char[]){0xfe,0x7f}, .value = 32766 },
{ .expected = (unsigned char[]){0xff,0x7f}, .value = 32767 },
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
struct WritePipe *wp = NULL;
unsigned char buf[expected_len];
FILE *file = open_write_pipe(&wp);
if (!file)
return 2;
if (!write_s16_le(file, data[i].value)) {
perror(func);
return 3;
}
fflush(file); /* important, otherwise read will get stuck */
int nbytes = data_from_write_pipe(wp, buf, (int) sizeof buf);
fclose(file);
if (nbytes != expected_len) {
printf("%s() failed on dataset %d (%d):\n", func, i, (int)data[i].value);
printf("expected to read %d bytes, got %d bytes\n", expected_len, nbytes);
return 1;
}
if (memcmp(data[i].expected, buf, expected_len) != 0) {
printf("%s() failed on dataset %d (%d):\n", func, i, (int)data[i].value);
printf("expected 0x%02x%02x, got 0x%02x%02x\n",
(unsigned)data[i].expected[0], (unsigned)data[i].expected[1],
(unsigned)buf[0], (unsigned)buf[1]);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_imgrgb_to_outbytes") && !strcmp(subtest, "int")) {
struct Bmpwrite bw = { 0 };
struct {
int src_channels;
bool src_has_alpha;
BMPFORMAT src_format;
int src_bitsperchannel;
bool out_64bit;
struct Colormask masks;
const unsigned char *imgbytes;
unsigned long long expected;
} data[] = {
{ .src_channels = 1, .src_has_alpha = false, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 8, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0xff, .blue = 0xff },
.masks.shift = { .red = 0, .green = 8, .blue = 16 },
.masks.bits = { .red = 8, .green = 8, .blue = 8},
.masks.maxval = { .red = 255.0, .green = 255.0, .blue = 255.0},
.imgbytes = (unsigned char[]){ 0x45 },
.expected = 0x00454545ULL,
},
{ .src_channels = 3, .src_has_alpha = false, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 8, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0xff, .blue = 0xff },
.masks.shift = { .red = 0, .green = 8, .blue = 16 },
.masks.bits = { .red = 8, .green = 8, .blue = 8},
.masks.maxval = { .red = 255.0, .green = 255.0, .blue = 255.0},
.imgbytes = (unsigned char[]){ 0x12, 0x34, 0x45 },
.expected = 0x00453412ULL,
},
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 8, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0xff, .blue = 0xff, .alpha = 0xff },
.masks.shift = { .red = 0, .green = 8, .blue = 16, .alpha = 24 },
.masks.bits = { .red = 8, .green = 8, .blue = 8, .alpha = 8},
.masks.maxval = { .red = 255.0, .green = 255.0, .blue = 255.0, .alpha = 255.0 },
.imgbytes = (unsigned char[]){ 0x12, 0x34, 0x45, 0x67 },
.expected = 0x67453412ULL,
},
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 8, .out_64bit = false,
.masks.mask = { .red = 0x3ff, .green = 0x3ff, .blue = 0x3ff, .alpha = 0x1 },
.masks.shift = { .red = 0, .green = 10, .blue = 20, .alpha = 30 },
.masks.bits = { .red = 10, .green = 10, .blue = 10, .alpha = 1 },
.masks.maxval = { .red = 1023.0 , .green = 1023.0 , .blue = 1023.0 , .alpha = 1.0 },
.imgbytes = (unsigned char[]){ 0x79, 0x11, 0xe6, 0xff },
.expected = 0x79b111e5ULL,
},
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 16, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0x1ff, .blue = 0x3ff, .alpha = 0x1f },
.masks.shift = { .red = 0, .green = 8, .blue = 17, .alpha = 27 },
.masks.bits = { .red = 8, .green = 9, .blue = 10, .alpha = 5 },
.masks.maxval = { .red = 255.0 , .green = 511.0 , .blue = 1023.0 , .alpha = 31.0 },
.imgbytes = (unsigned char[]){ 0x79, 0x00, 0x32, 0x09, 0x45, 0xf5, 0x00, 0x80 },
.expected = 0x87a81200ULL,
},
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_INT,
.src_bitsperchannel = 32, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0x1ff, .blue = 0x3ff, .alpha = 0x1f },
.masks.shift = { .red = 0, .green = 8, .blue = 17, .alpha = 27 },
.masks.bits = { .red = 8, .green = 9, .blue = 10, .alpha = 5 },
.masks.maxval = { .red = 255.0 , .green = 511.0 , .blue = 1023.0 , .alpha = 31.0 },
.imgbytes = (unsigned char[]){ 0xf8, 0x15, 0xd3, 0x89, 0xcc, 0x15, 0x6f, 0x55,
0x5f, 0xd5, 0xfb, 0x1d, 0x19, 0xc8, 0x60, 0x36 },
.expected = 0x38f0ab89ULL,
},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
bw.source_channels = data[i].src_channels;
bw.source_has_alpha = data[i].src_has_alpha;
bw.source_format = data[i].src_format;
bw.source_bitsperchannel = data[i].src_bitsperchannel;
bw.out64bit = data[i].out_64bit;
memcpy(&bw.cmask, &data[i].masks, sizeof data[i].masks);
unsigned long long result = s_imgrgb_to_outbytes(&bw, data[i].imgbytes);
if (result != data[i].expected) {
printf("%s() - %s failed on dataset %d:\n", func, subtest, i);
printf("expected 0x%016llx, got 0x%016llx\n",
data[i].expected, result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_imgrgb_to_outbytes") && !strcmp(subtest, "float")) {
struct Bmpwrite bw = { 0 };
struct {
int src_channels;
bool src_has_alpha;
BMPFORMAT src_format;
int src_bitsperchannel;
bool out_64bit;
struct Colormask masks;
const float *imgvals;
unsigned long long expected;
} data[] = {
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_FLOAT,
.src_bitsperchannel = 32, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0x1ff, .blue = 0x3ff, .alpha = 0x1f },
.masks.shift = { .red = 0, .green = 8, .blue = 17, .alpha = 27 },
.masks.bits = { .red = 8, .green = 9, .blue = 10, .alpha = 5 },
.masks.maxval = { .red = 255.0 , .green = 511.0 , .blue = 1023.0 , .alpha = 31.0 },
.imgvals = (float[]){ 0.3, 1.0, 0.78923, 0.6 },
.expected = 0x9e4fff4dULL,
},
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_FLOAT,
.src_bitsperchannel = 32, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0x1ff, .blue = 0x3ff, .alpha = 0x1f },
.masks.shift = { .red = 0, .green = 8, .blue = 17, .alpha = 27 },
.masks.bits = { .red = 8, .green = 9, .blue = 10, .alpha = 5 },
.masks.maxval = { .red = 255.0 , .green = 511.0 , .blue = 1023.0 , .alpha = 31.0 },
.imgvals = (float[]){ 0.3, 3.2, 0.78923, -1.5 },
.expected = 0x64fff4dULL,
},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
bw.source_channels = data[i].src_channels;
bw.source_has_alpha = data[i].src_has_alpha;
bw.source_format = data[i].src_format;
bw.source_bitsperchannel = data[i].src_bitsperchannel;
bw.out64bit = data[i].out_64bit;
memcpy(&bw.cmask, &data[i].masks, sizeof data[i].masks);
unsigned long long result = s_imgrgb_to_outbytes(&bw, (unsigned char*)data[i].imgvals);
if (result != data[i].expected) {
printf("%s() - %s failed on dataset %d:\n", func, subtest, i);
printf("expected 0x%016llx, got 0x%016llx\n",
data[i].expected, result);
return 1;
}
}
return 0;
}
if (!strcmp(func, "s_imgrgb_to_outbytes") && !strcmp(subtest, "s2.13")) {
struct Bmpwrite bw = { 0 };
struct {
int src_channels;
bool src_has_alpha;
BMPFORMAT src_format;
int src_bitsperchannel;
bool out_64bit;
struct Colormask masks;
const unsigned char *imgbytes;
unsigned long long expected;
} data[] = {
{ .src_channels = 4, .src_has_alpha = true, .src_format = BMP_FORMAT_S2_13,
.src_bitsperchannel = 16, .out_64bit = false,
.masks.mask = { .red = 0xff, .green = 0x7ff, .blue = 0x3ff, .alpha = 0x7 },
.masks.shift = { .red = 0, .green = 8, .blue = 19, .alpha = 29 },
.masks.bits = { .red = 8, .green = 11, .blue = 10, .alpha = 3 },
.masks.maxval = { .red = 255.0 , .green = 2047.0 , .blue = 1023.0 , .alpha = 7.0 },
.imgbytes = (unsigned char[]){ 0x81, 0x0e, 0x7d, 0x67, 0x41, 0x19, 0x68, 0xb1 },
.expected = 0x193fff74ULL,
},
};
for (int i = 0; i < ARRAY_LEN(data); i++) {
bw.source_channels = data[i].src_channels;
bw.source_has_alpha = data[i].src_has_alpha;
bw.source_format = data[i].src_format;
bw.source_bitsperchannel = data[i].src_bitsperchannel;
bw.out64bit = data[i].out_64bit;
memcpy(&bw.cmask, &data[i].masks, sizeof data[i].masks);
unsigned long long result = s_imgrgb_to_outbytes(&bw, (unsigned char*)data[i].imgbytes);
if (result != data[i].expected) {
printf("%s() - %s failed on dataset %d:\n", func, subtest, i);
printf("expected 0x%016llx, got 0x%016llx\n",
data[i].expected, result);
return 1;
}
}
return 0;
}
fprintf(stderr, "Invalid test '%s'\n", func);
return 2;
}