This is one of my first successful C projects. A C library that construts an image, lets you access pixels individually or use functions to draw shapes, then save it as a .bmp file.
Initially, a long time ago, I made module that would take an array of pixels and save it as a .bmp file.
Then, I restructured it to be a library. Both a static library (.a) and a dynamic library (.so).
Then, I made the thing more useful, better, faster. (See features)
I completed bmp_draw_rect, a function that can actually draw to the image.
After that, I automated the build process to build .dll library in OS Windows using MSVC compiler.
Then I could write a python wrapper for the library that works on both Windows and Linux, so I did that.
A .bmp picture file has the rows padded to 4 bytes. I made it so that the picture is row-padded from the beginning, that is, the padding is present in the pixel array in memory when the picture is still being generated.
How is the pixel's position in the array calculated?
pixel(x, y, img)macro, that returns an l-value of a pixel struct atx,yin theimg's pixel array (imgbeing an image struct)
Whats the benefit of this?
- The image is written to the
.bmpfile using only threefwritecalls. (File header, DIB header, the data) - This makes it fast because there is very little I/O overhead
I developed the library using gcc 13.1.0, but really any version should work. Also I used GNU Make 4.3.
maketo compile everything:libbmap.a- static version of the librarylibbmap.so- dynamic version of the libraryexample.bin(it will be linked against the.sodynamic library)
make libbmap.ato compile the static library onlymake libbmap.soto compile the dynamic library onlymake example_static.binto compile all-static version ofexample.bin- this puts the necessary parts of C standard library into the executable (not recommended)
make cleanto remove all binaries (so you can re-start the build process froms scratch)
Know that make run is for development only - I use a very specific environment that might not work for you
While you could use any compiler to build this library (perhaps MinGW), I chose MSVC. Therefore, to use the provided build script, you need to have Microsoft Visual Studio Build Tools (when installing, select Desktop development with C++ workload).
Here are the versions I have, although, sice this build process is very simple, it should not matter if you have older/newer.
Microsoft (R) C/C++ Optimizing Compiler Version 19.35.32215 for x64
Microsoft (R) Incremental Linker Version 14.35.32215.0
Microsoft (R) Library Manager Version 14.35.32215.0
Then, in the VS Developer command prompt, you can run these commands:
buildto build the dynamic library (.dll) andexample.exelinked against itbuild staticto build the static library (.lib) andexample.exelinked against itbuild cleanto remove all binaries (sou you can re-build from scratch)build runto build the dynamic library + build and runexample.exe
Note: A .lib file is also created alongside the .dll dynamic library. However, this file is not a static library, rather a so-called import library. This is what you then link your program against (you still need the .dll, the code is in it).
Note 2: When executing the build command, the command prompt actually executes build.bat.
Note 3: To build 64-bit binaries, you need to open x64 Native Tools Comand Prompt for VS 2022.
Like stated here, the MSVC tools build 32-bit applications by default:
Visual Studio includes C++ compilers, linkers, and other tools that you can use to create platform-specific versions of your apps that can run on 32-bit, 64-bit, or ARM-based Windows operating systems. Other optional Visual Studio workloads let you use C++ tools to target other platforms, such as iOS, Android, and Linux. The default build architecture uses 32-bit, x86-hosted tools to build 32-bit, x86-native Windows code. However, you probably have a 64-bit computer. When Visual Studio is installed on a 64-bit Windows operating system, additional developer command prompt shortcuts for the 64-bit, x64-hosted native and cross compilers are available. You can take advantage of the processor and memory space available to 64-bit code by using the 64-bit, x64-hosted toolset when you build code for x86, x64, or ARM processors.
The example.c file is compiled to example.bin (perhaps example.exe). When example.bin is run, it creates example.bmp, an example image.
In the near future, I want to:
- Implement more drawing functions
- ✅ Work on the python wrapper (add missing null checks, make it more pythonic, etc.)
- ✅ Automate + document the build process on OS Windows
To add a new function, one must:
- declare it in
bmp.h - implement it in a spearate
.cfile - write a recipe for it in
Makefile- that will most probably go like this:
# compile my_module my_module.o: my_module.c $(CC) $(CFLAGS) -fPIC -c $< - in
Makefile, add it to theBMP_LIB_MODULESvariable - in
build.bat, add it to themodulesvariable
That way, upon finishing implementation, the function is exported to the library.
The library can be used in python. Here's how to do it (Note: it's the same example as when you run libbmap.py directly):
from python import libbmap as bmp
img = bmp.Image(100, 200)
img.draw_rectangle(bmp.Point(50, 50), bmp.Point(70, 80), bmp.Color(255, 0, 0))
img.save_bmp("imported_lib_example.bmp")- the
importstatement importslibbmap.pyas a module from thepythonfolder- to do it like this, you should place your
.pyfile into the root directory - this is possible because the folder containt
__init__.py
- to do it like this, you should place your
- all you need is in the
libbmap.pyfile, however you import it - by default, the wrapper expects you to have the shared library file (
.so/.dll) in the same folder whence you run the.pyfile - you can alter this behavior by changing the
SO_FILEvariable at the start oflibbmap.py
- Jan Marjanovic's bmp inspector python script - helped me find problems in my generated files