SDL将图像嵌入程序可执行文件中

18

使用SDL在程序中嵌入图片并在运行时使用,这是否可能?

例如,我有一个程序,在启动时显示一个包含标志和版权信息的启动画面。与其将此图像放在位图文件中,并使用SDL_LoadBMP将其加载到SDL_Surface中,我希望将图像嵌入程序二进制文件中,以防止某人潜在地更改启动画面和版权名称。

有什么建议可以做到这一点吗?示例代码将非常好。


只是顺便提一下:如果有人想要更改他们的启动画面,他们将会更改你的启动画面。这不应该是你这样做的唯一动机。 - user142162
2
这是真的,如果有人真的想要,他们可能会做到。然而,我的另一个动机是该程序是一个小工具,需要便携和易于使用,适用于那些计算机技能较少的人群,因此将整个程序作为单个文件比必须放置在正确位置的小图像文件夹更可取。 - Scott
这应该是可能的(不是琐碎,但是可能的)。在我看来,一个正确配置的SDL_RWOps与调用SDL_LoadBMP_RW()可以从任何你配置它的地方获取图像,包括在编译时构建的静态内部缓冲区。无论这是否“解决”了您正在识别的问题是另一回事。 - WhozCraig
刚刚看了一下这些代码,据我理解,如果我在源代码中将位图图像转换为字符数组,我可以使用这些命令使其在运行时从常量变量加载位图? - Scott
@Scott 没错,而且Dietrich在下面提供了一些获取静态图像的方法。一旦你在可执行文件中以二进制格式获得了静态图像,只需要设置一个SDL_RWOps来使用一个自定义函数集(由你编写),该函数集将该图像作为其数据“源”。你明白我的意思。 - WhozCraig
确实如此,只需要设置SDL_RWOps以将数据检索到流中,并使用SDL_LoadBMP_RW从SDL_RWOps流中加载位图。非常感谢。 - Scott
3个回答

19

将文件嵌入可执行文件很容易,但有一些需要注意的地方。有几种方法可以实现,包括一些便携式和非便携式的方法。

使用 #embed

据报道,这将成为C23的一部分。它也可能在C++26中出现。请检查您的编译器是否支持此功能。未来,这可能是嵌入二进制数据最具可移植性和直接的方式。

static const unsigned char IMAGE_DATA[] = {
#embed "myimage.bmp
};

有关功能提案,请参见WG14 n2592

优点:最简单、最容易的。

缺点:你的编译器可能还不支持此功能。

将图像转换为C代码

编写一个脚本,将图像转换为C语言中的常量数组。 在Python中,该脚本看起来会像这样:

#!/usr/bin/env python3
print("static const unsigned char IMAGE_DATA[] = {{{}}};".format(
        ",".join(str(b) for b in open("myimage.bmp", "rb").read())))

只需将输出导入一个*.h文件中,并从另一个文件中包含该文件。您可以使用sizeof(IMAGE_DATA)获取文件的大小。

优点:可移植性强

缺点:需要安装Python,如果数组太大而无法编译,则无法工作,需要向构建系统添加自定义步骤

将图像转换为对象文件

这更具平台依赖性。 在具有GNU binutils工具链(例如Linux)的平台上,可以使用objcopy,我认为bin2obj适用于Microsoft工具链。

优点:在任何地方都有效

缺点:不可移植,需要向构建系统添加自定义步骤,自定义步骤可能难以正确实现

对于带有objcopy的GNU binutils工具链

objcopy程序允许您将binary指定为输入格式,但是然后您需要明确指定架构...因此,您将不得不修改i386和x64版本可执行文件的命令。

$ objcopy --input binary --output elf32-i386 --binary-architecture i386 \
    myimage.bmp myimage.o

你可以使用以下声明从 C 中获取数据:

// Ignore the fact that these are char...
extern char _binary_myimage_bmp_start, _binary_myimage_bmp_end;

#define MYIMAGE_DATA ((void *) &_binary_myimage_bmp_start)
#define MYIMAGE_SIZE \
    ((size_t) (&_binary_myimage_bmp_end - &_binary_myimage_bmp_start))

使用汇编器指令

具有讽刺意味的是,在汇编语言中嵌入静态文件相当容易。汇编器通常具有像.incbin这样的指令(适用于GAS和YASM)。

优点:适用于任何平台

缺点:不可移植,汇编语法在不同平台上不同

(Windows)将文件作为资源嵌入

在Windows上,您可以将资源嵌入EXE文件中,然后使用库调用获取资源。

优点:如果您在Windows上,则可能是最简单的方法

缺点:只适用于Windows操作系统


我曾考虑过在Windows上使用资源,但我的程序是跨平台的,因此我正在寻找更通用的解决方案。由于我缺乏汇编知识并且图像很小,所以我认为我可以使用“将图像转换为C”选项。然而,如果您能指点我正确的方向,我也不介意了解有关对象文件方法的更多信息。我的主要开发平台是Linux。 - Scott
当然,我添加了更多关于如何使用objcopy的指令。 - Dietrich Epp
太好了!但是当我得到那个 image.h 文件后,我该如何在 c++ 中使用它呢?例如,在 qt 中通常可以将字符串设置为图像文件路径(string str1 = str0 + "splash.png";QPixmap pixmap(str1.c_str());)。现在我应该怎么做才能使用 image.h 文件呢?谢谢。 - Daniel
@Daniel:你需要阅读Qt文档,了解如何从内存中读取图像。 - Dietrich Epp
2
GIMP有一个导出图像到C格式的功能,如果有人感兴趣的话。 - j0h
显示剩余2条评论

4
你可以将图像导出为.xpm格式(在gimp中),并将其包含到你的代码中。但是你需要SDL_Image.h来将其加载为SDL_Surface。
正如this文档所述,非常简单:
//To create a surface from an XPM image included in C source, use:

SDL_Surface *IMG_ReadXPMFromArray(char **xpm);

一个C/C++示例:
#include <SDL/SDL.h>
#include "test.xpm"
#include <SDL/SDL_image.h>

SDL_Surface *image;
SDL_Surface *screen;

int main(int argc, char **argv)
{
    SDL_Init(SDL_INIT_EVERYTHING);
    screen = SDL_SetVideoMode(800,600,32,SDL_SWSURFACE);
    image = IMG_ReadXPMFromArray(test_xpm); //the .xpm image is a char array. "test_xpm" is the name of the char array
    SDL_Rect offset;
    offset.x = 0;
    offset.y = 0;
    SDL_BlitSurface(image,NULL,screen,&offset);
    SDL_Flip(screen);
    SDL_Delay(5000);


    return 0;
}

我希望这能有所帮助。


3

使用GIMP,你可以将图片保存为C代码。


2
这是正确的,然而GIMP只能导出原始像素数据。而我需要保持文件以正确的图像格式在SDL中使用。 - Scott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接