【SDL游戏编程入门第九卷】颜色键控ColorKeying

请添加图片描述

一、前言

在屏幕上渲染多个图像时,通常需要具有透明背景的图像。幸运的是,SDL 提供了一种简单的方法来执行此操作: 颜色键控。其实现在这种用的少,纹理也能自动处理 PNG 的透明部分,不过作为基础知识,可以了解一下

二、使用颜色键控 (Color Keying)

本卷使用纹理资源

/**
 * @brief 加载纹理
 * @param path:资源路径
 */
SDL_Texture* LoadTexture(const char* path, bool enableColorKey)
{
	// 最终纹理
	SDL_Texture* newTexture = nullptr;
	SDL_Surface* loadedSurface = IMG_Load(path);
	if (!loadedSurface)
		std::cout << "[Error]: Unable to load image " << path << " SDL Error: " << SDL_GetError() << std::endl;
	else
	{
		// 颜色键控
		if (enableColorKey)
			SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, 255, 0, 255));
		// 利用表面创建纹理
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (!newTexture)
			std::cout << "[Error]: Unable to create Texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		// 释放表面
		SDL_FreeSurface(loadedSurface);
	}
	// 返回最终优化的资源
	return newTexture;
}

我们在从图像创建纹理之前,先用 SDL_SetColorKey 对图像进行颜色键控。第一个参数是我们想要用于颜色键的表面,第二个参数涵盖我们是否要启用颜色键控,最后一个参数是我们想要进行颜色键控的像素。

从 RGB 颜色创建像素的最跨平台方法是使用 SDL_MapRGB。第一个参数是我们 想要像素。幸运的是,加载的图面具有格式成员变量。最后三个变量是要映射的颜色的红色、绿色和蓝色分量。这里 我们正在映射紫色,即红色 255、绿色 0、蓝色 255。

注意:
这里我对 LoadTexture 做了小小的改动,增添了一个是否启用颜色键控的选项
你也可以封装成类,但我不想弄的太复杂,这里仅针对单一功能进行介绍

三、示例代码

/* 此源代码版权归 AnnihilateSword (2022-*)所有,未经书面许可不得转载。*/

// 使用 SDL 和 iostream
#include <SDL.h>
#include <SDL_image.h>
#include <iostream>

// 链接库
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
#pragma comment(lib, "SDL2_image.lib")

// 屏幕尺寸常量
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

bool Init();								// 启动 SDL 并创建窗口
bool LoadMedia();							// 加载媒体
SDL_Texture* LoadTexture(const char* path, bool enableColorKey);	// 加载纹理
void Close();								// 清理资源,关闭窗口以及SDL

SDL_Window* gWindow = nullptr;				// 主窗口
SDL_Renderer* gRenderer = nullptr;			// 主窗口渲染器

SDL_Texture* gTextureBlue = nullptr;		// 需要加载的纹理(表面)
SDL_Texture* gTextureMan = nullptr;			// 需要加载的纹理(表面)

int main(int argc, char* argv[])			// 必须要填写此参数,不然会出现链接错误
{
	// 初始化 SDL
	if (Init())
	{
		// 加载媒体资源
		if (!LoadMedia())
			std::cout << "[Error]: Failed to load media!" << std::endl;
		
		// 窗口循环
		SDL_Event e;
		bool quit = false;
		while (quit == false)
		{
			// 处理事件
			while (SDL_PollEvent(&e))
			{
				if (e.type == SDL_QUIT)
					quit = true;
			}

			// --- rendering -----------------------------------------------------------------------
			// 设置渲染器绘制颜色(黑色)
			SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 255);
			// 清屏
			SDL_RenderClear(gRenderer);

			SDL_Rect topLeftViewport{ 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
			// 最后一个参数为 nullptr,会填充所在的视口区域
			SDL_RenderSetViewport(gRenderer, &topLeftViewport);
			SDL_RenderCopy(gRenderer, gTextureBlue, nullptr, nullptr);

			SDL_Rect topRightViewport{ SCREEN_WIDTH / 3, SCREEN_HEIGHT / 2, 128, 128 };
			SDL_RenderSetViewport(gRenderer, &topRightViewport);
			// 最后一个参数为 nullptr,会填充所在的视口区域
			SDL_RenderCopy(gRenderer, gTextureMan, nullptr, nullptr);

			// 更新屏幕
			SDL_RenderPresent(gRenderer);
		}
	}
	// 清理资源
	Close();
	return 0;
}

/**
 * @brief 启动 SDL 并创建窗口
 */
bool Init()
{
	// 初始化 SDL
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		std::cout << "[Error]: SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
		return false;
	}
	else
	{
		// 设置纹理线性过滤
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"))
			std::cout << "[Warning]: Linear texture filtering not enabled!" << std::endl;

		// 创建窗口
		gWindow = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
		if (!gWindow)
		{
			std::cout << "[Error]: Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
			return false;
		}
		else
		{
			// 创建窗口渲染器
			gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);  // -1 初始化支持所请求标志的第一个
			if (!gRenderer)
			{
				std::cout << "[Error]: Renderer could not bo created! SDL_Error: " << SDL_GetError() << std::endl;
				return false;
			}
			else
			{
				// 初始化渲染器绘制颜色(蓝色),这次在主循环中设置
				//SDL_SetRenderDrawColor(gRenderer, 51, 76, 204, 255);

				// 初始化 PNG 加载
				int imgFlags = IMG_INIT_PNG;
				if (!(IMG_Init(imgFlags) & imgFlags))
				{
					std::cout << "[Error]: SDL_image could not initialize! SDL_image Error: " << IMG_GetError() << std::endl;
					return false;
				}
			}
		}
	}
	return true;
}

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	gTextureBlue = LoadTexture(R"(res\textures\blue.png)", false);
	if (!gTextureBlue)
	{
		std::cout << "[Error]: Failed to load texture blue!" << std::endl;
		return false;
	}
	gTextureMan = LoadTexture(R"(res\textures\man.png)", true);  // 启用颜色键控
	if (!gTextureMan)
	{
		std::cout << "[Error]: Failed to load texture man!" << std::endl;
		return false;
	}
	return true;
}

/**
 * @brief 加载纹理
 * @param path:资源路径
 */
SDL_Texture* LoadTexture(const char* path, bool enableColorKey)
{
	// 最终纹理
	SDL_Texture* newTexture = nullptr;
	SDL_Surface* loadedSurface = IMG_Load(path);
	if (!loadedSurface)
		std::cout << "[Error]: Unable to load image " << path << " SDL Error: " << SDL_GetError() << std::endl;
	else
	{
		// 颜色键控
		if (enableColorKey)
			SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, 255, 0, 255));
		// 利用表面创建纹理
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (!newTexture)
			std::cout << "[Error]: Unable to create Texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		// 释放表面
		SDL_FreeSurface(loadedSurface);
	}
	// 返回最终优化的资源
	return newTexture;
}

/**
 * @brief 清理资源,关闭窗口以及SDL
 */
void Close()
{
	// 清理纹理资源
	if (gTextureBlue)
	{
		SDL_DestroyTexture(gTextureBlue);
		gTextureBlue = nullptr;
	}
	if (gTextureMan)
	{
		SDL_DestroyTexture(gTextureMan);
		gTextureMan = nullptr;
	}
	// 销毁窗口渲染器
	if (gRenderer)
	{
		SDL_DestroyRenderer(gRenderer);
		gRenderer = nullptr;
	}
	// 销毁窗口
	if (gWindow)
	{
		SDL_DestroyWindow(gWindow);
		gWindow = nullptr;
	}
	// 退出 IMG 和 SDL
	IMG_Quit();
	SDL_Quit();
}

1. 运行结果

不使用颜色键控

使用颜色键控



本节内容就到这里了,下卷会继续分享 SDL 的基本使用

The End.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值