MLX Transparency

over 1 year

2023 08

This guide will explain how to implement transparency with the minilibx library

Clear as crystal

Welcome readers to another tutorial.

Lately I've seen a few people asking about how to implement transparency, and why it works differently across multiple platforms.
Here I will show you how to solve this problem.
First let's get started, as always, download the zip, you will see the basic functions and if you run the program you will see there is one white pixel in the middle of the window.

This guide will help you implement transparency on your program. Transparency doesn't necessarily involves images, but for the most part it does, so let's start by placing an image on the window. For this guide I will use images.

t_img	new_file_img(char * path, t_win window) {
	t_img image;

	image.win = window;
	image.img_ptr = mlx_xpm_file_to_image(window.mlx_ptr, path, &image.w, &image.h);
	if (!image.img_ptr)
		write(2, "File could not be read\n", 23);
	else
		image.addr = mlx_get_data_addr(image.img_ptr, &(image.bpp),
			&(image.line_len), &(image.endian));
	return (image);
}

Remember, because we can't return NULL you need to check image.img_ptr allways after calling this function, or if you prefer, change the return to a pointer and allocate the memory.

Now we can load all the xpm images we want with this function, I will start by loading a background image to my program.
Add to the main the following:

image = new_file_img("lotr_map.xpm", tutorial);
if (!image.img_ptr)
	return (2);
mlx_put_image_to_window (image.win.mlx_ptr, image.win.win_ptr, image.img_ptr, 0, 0);

The code above will place the background to my window (I did change the size of the window to fit exactly the dimension of the background 1000x650)
Now that we have a background we could assume that we can just put another image on top, and because the image has transparent pixels, then the image will become transparent, so let's try that:

Just load another image after the background... I choose to put a ring image on top of the map background.
On my Darwin system it worked, the ring is there on the top left corner!!! The order is respected and the transparency is working, now let's try the same code on my Linux Fedora 37:

The result is different, that doesn't look transparent at all. But don't worry, there is a way to fix this.

The solution is to have only one image, you will copy every pixel from all the components of your program to the one image, and at the end you will place that image to your window, this way you can have many layers.
First let's start by creating that image, I will call it "base image".
You need to copy the background to your base image, then on top of the background all the color pixels of your desired components.

Create the function to copy all the pixels from one image to another image. This is like memcpy but for images.


unsigned int	get_pixel_img(t_img img, int x, int y) {
	return (*(unsigned int *)((img.addr
			+ (y * img.line_len) + (x * img.bpp / 8))));
}

void	put_img_to_img(t_img dst, t_img src, int x, int y) {
	int i;
	int j;

	i = 0;
	while(i < src.w) {
		j = 0;
		while (j < src.h) {
			put_pixel_img(dst, x + i, y + j, get_pixel_img(src, i, j));
			j++;
		}
		i++;
	}
}


The first function will get the desired pixel so you can copy that color to dst.

Let's see if this works, instead of putting all the images to the window, we must create a base image and use our new function to place the other images to the base image, and finally put the base image to the window.

int main(void)
{
 	t_win	tutorial;
	t_img	base_image;
	t_img	bg;
	t_img	ring;

	signal(SIGINT, sig_handler);
	tutorial = new_window(1000, 650, "transparency");
	if (!tutorial.win_ptr)
		return (2);
	base_image = new_img(1000, 650, tutorial);

	{
		bg = new_file_img("lotr_map.xpm", tutorial);
		if (!bg.img_ptr)
			return (2);
		put_img_to_img(base_image, bg, 0, 0);
	}
	{
		ring = new_file_img("ring.xpm", tutorial);
		if (!ring.img_ptr)
			return (2);
		put_img_to_img(base_image, ring, 50, 100);
	}
	mlx_put_image_to_window (base_image.win.mlx_ptr, base_image.win.win_ptr, base_image.img_ptr, 0, 0);
	mlx_loop(tutorial.mlx_ptr);
	mlx_destroy_window(tutorial.mlx_ptr, tutorial.win_ptr);
	return (0);
}

Even after we do this, you will see that it didn't work, the transparent pixels are still black! :(
But we are almost done, we can just "ignore" transparent pixels, and NOT put any color to our base image, that way, the color that was before will not be overwritten!!!

Since I always want to implement transparency I will implement this on the most basic function, put_pixel_img, and I will NOT put any pixel when the color is transparent.

The condition looks like this:

if (color == (int)0xFF000000)
	return ;

put_pixel_img could look like this:

void	put_pixel_img(t_img img, int x, int y, int color)
{
	char	*dst;

	if (color == (int)0xFF000000)
		return ;
	if (x >= 0 && y >= 0 && x < img.w && y < img.h) {
		dst = img.addr + (y * img.line_len + x * (img.bpp / 8));
		*(unsigned int *) dst = color;
	}
}

And the result isssssss:

Cheers and have fun with ur projects!