mirror of
https://github.com/rupertwh/bmplib.git
synced 2026-04-10 08:12:35 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7910ac36b | ||
|
|
7ece494541 | ||
|
|
c8ead48e33 | ||
|
|
8b38742ea3 | ||
|
|
3a673da403 | ||
|
|
fabbe4f8af | ||
|
|
22ef9d73aa | ||
|
|
abd3ea2db2 | ||
|
|
3c5723b740 | ||
|
|
8dd7182bf1 | ||
|
|
07d3bf6086 | ||
|
|
5cd33b22ca | ||
|
|
10657851d1 | ||
|
|
044fa148ed | ||
|
|
b9244c0766 | ||
|
|
84a0f0f49d | ||
|
|
1b67440d29 | ||
|
|
5166bdb1e7 | ||
|
|
c02804cdbf | ||
|
|
fd8583d568 | ||
|
|
845223bf62 | ||
|
|
952c10324f | ||
|
|
668cc352e3 | ||
|
|
1bba98e395 | ||
|
|
0815eb3a7b | ||
|
|
94dceb5812 | ||
|
|
c733595aed | ||
|
|
920ba8ea7e | ||
|
|
0882f1796a | ||
|
|
da95bcb0ae | ||
|
|
e5584c3b0f | ||
|
|
3af484b238 | ||
|
|
6c33d975b5 | ||
|
|
0ab592c357 | ||
|
|
e9b047b857 | ||
|
|
71b3853327 | ||
|
|
5ce902ce1b | ||
|
|
abf93c1a77 | ||
|
|
bc7dd1423c | ||
|
|
100715fe9d | ||
|
|
e195d1d918 | ||
|
|
3989e07e05 | ||
|
|
df8d9e44ca | ||
|
|
7d2ff7fca5 | ||
|
|
2ba0910acc | ||
|
|
7494855af6 | ||
|
|
1d9a3faad4 | ||
|
|
3d31aa93cd | ||
|
|
91d93cba9b | ||
|
|
b43fd940e6 | ||
|
|
61a4caf6ef | ||
|
|
4942795e14 | ||
|
|
52801cd5d5 | ||
|
|
0e4e611955 |
257
API-full.md
257
API-full.md
@@ -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) */
|
||||
|
||||
@@ -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`.
|
||||
|
||||
38
README.md
38
README.md
@@ -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
|
||||
|
||||
43
bmp-common.c
43
bmp-common.c
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
70
bmp-common.h
70
bmp-common.h
@@ -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
360
bmp-read-icons.c
Normal 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
28
bmp-read-icons.h
Normal 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);
|
||||
@@ -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
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
341
bmp-read.c
341
bmp-read.c
@@ -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
|
||||
*****************************************************************************/
|
||||
|
||||
@@ -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
|
||||
|
||||
366
bmp-write.c
366
bmp-write.c
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
85
bmplib.h
85
bmplib.h
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
64
meson.build
64
meson.build
@@ -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
145
test-fileio-pipes.c
Normal 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
29
test-fileio-pipes.h
Normal 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
247
test-read-conversions.c
Normal 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
377
test-read-io.c
Normal 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
BIN
test-write-helper.ods
Normal file
Binary file not shown.
431
test-write-io.c
Normal file
431
test-write-io.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user