游戏引擎学习第164天

仓库:https://gitee.com/mrxiao_com/2d_game_4

回顾和今天的计划

尽管昨天我们进行了一次关于资产处理器的库的演示,因为有些人想看看如何使用 stb TrueType,所以我展示了如何使用它。如果你对我们唯一一次在游戏中使用库感兴趣,那么昨天就是你想要看到的时刻。

那么,今天我们要做什么呢?今天我将向大家展示如何在没有库的情况下做同样的事情。为什么不呢?也许这能让你更欣赏 stb TrueType 的便利。

项目中不允许使用任何库,但对于资产处理器我没有问题,因为它不会出现在最终用户的机器上,所以我们可以这样做。如果你想使用 stb TrueType 的实现,完全没有问题,可以随意使用它,尽情享受在资产处理器中使用所有库的乐趣。

在资产处理器中使用库的优缺点

虽然资产处理器是离线运行的,但对于不喜欢使用库的原因,我更倾向于保持代码的可运行性和便携性。使用库会有效率等方面的问题,这些都是我不愿意在游戏中使用库的原因。不过,当涉及到资产处理器时,这些问题有所减轻,因为资产处理器并不需要在不同的机器上运行,不需要考虑 64 位和 32 位的兼容性,也不需要非常便携。资产处理器只需要在开发机器上运行,处理完游戏的资产后,便可以完成任务。

然而,即使如此,还是需要考虑到资产处理器作为一个工具的重要性。如果这个工具将来可能会在不同的平台上使用,或者需要在不同的开发环境中使用,那么还是应该考虑和游戏本身一样的规则。我通常会选择那些经过精心设计、容易实现并且支持跨平台的库,比如 SDB TrueType。这样的库不需要复杂的构建过程,而且非常方便。虽然资产处理器的任务相对较轻,但如果游戏扩展到 3D,资产处理器的复杂度会增加,可能变成一个较为重量级的项目。所以,尽管我们在中将资产处理器当作一个独立的部分不太关注,但在真正的游戏项目中,资产处理器的确会成为一个复杂且不可忽视的部分。

今天我想展示的是,如果不想使用库,应该如何提取字体。我们在主代码库中没有使用库。那么,如果我们不使用库,我们该如何提取这些字体呢?一种方法是自己编写 TrueType 字体解析器,当然这是一个可行的方案,但也可能会比较麻烦。另一种方法是依赖操作系统来提取字体。无论在什么操作系统上,都有一种方式能够从系统中提取字体信息。所以,资产处理器也可以直接从 Windows 中获取字体。

那么,如何在 Windows 中实现这一点呢?

使用 Windows 调用来绘制字体

Windows实际上提供了许多与字体相关的API。例如,TextOut函数可以将任意字符串渲染为一个HDC(设备上下文)。在之前的项目中,我们也使用过HDC,特别是在初次设置窗口并希望将内容绘制到窗口时。HDC是"设备上下文"的缩写,是Windows图形设备接口(GDI)中用来执行图形绘制的上下文。

如果我们想从Windows中提取字体,可以调用TextOut函数,将文本绘制到一个HDC中。接下来,问题就是:能否将这个HDC的内容绘制到我们选择的位图上?例如,我们希望这个位图能够在Windows的后台呈现。如果我们能够做到这一点,就可以让Windows绘制字体并从中提取这些字体。

这个方法的原则在任何操作系统中都适用:无论操作系统提供了什么机制来绘制字体,我们都可以使用它来实现类似的功能。

为了解决这个问题,我们有几种方法可以尝试。最慢的方法之一是使用GetPixel函数,尽管这种方法非常慢,并且可能并不是最佳选择,但它可以作为一个实验性的方法,以便了解如何实现这一功能。

GetPixel:获取逐像素渲染字体的最简单方法

最简单的方法是使用一个普通的HDC,而不需要特别创建一个自定义的HDC。如果我们不想花时间去弄清楚如何创建一个特殊的HDC,那么就可以选择使用任何一个普通的HDC。我们可以通过查看如何创建普通的HDC来实现这一点,然后调用GetPixel函数,按顺序读取每一个像素。由于这是一个资产处理器,我们可以接受这种方法,虽然速度很慢,但只要我们只做一次提取字体和脊柱的操作就可以了。所以,虽然这种方法效率不高,但在处理一次性提取时是可行的。

在完成这个方法之后,我们可以再决定是否需要进一步优化,使用更高级的HDC处理方式。如果需要获取一个HDC,通常的做法是通过调用CreateDC函数来实现。

CreateCompatibleDC 和 CreateCompatibleBitmap

我们可以通过调用CreateCompatibleDC来创建一个兼容的设备上下文(DC)。这将允许我们使用一个我们已经知道的设备上下文,比如屏幕设备上下文,作为基础。虽然也有其他的方法可以实现这一点,但这种方法通常较为简便。如果我们知道已有的设备上下文大致符合我们的要求,我们就可以直接使用它。

另外一种方法是创建一个窗口并使用该窗口的设备上下文。还有其他许多方式可以实现这个目标,但总体来说,我们会选用最适合当前任务的方法。

根据文档,在应用程序使用内存设备上下文(Memory DC)进行绘图操作之前,需要先选择一个与其尺寸和颜色要求匹配的位图。这意味着,在创建兼容的设备上下文之后,我们需要使用CreateCompatibleBitmap函数,指定位图的宽度、高度和颜色格式,来创建一个兼容的位图,并将其选入设备上下文中。这个过程将确保设备上下文可以用于绘制操作。

所以,我们的操作步骤大致是:首先创建兼容的设备上下文,然后创建与之匹配的位图,并将位图选入该设备上下文中。

在 LoadGlyphBitmap 中定义备用代码路径,通过 Windows 调用渲染字体

我们可以选择使用Windows中的字体,或者选择使用STB TrueType库。为了实现这一点,可以在代码中通过条件编译来决定是否包含Windows的相关头文件。具体来说,如果选择使用Windows字体,我们会包含windows.h,否则将使用STB TrueType库。

在STB TrueType的部分,我们会修改LoadGlyphBitmap函数,使其不依赖STB TrueType。这样做的目的是如果选择使用Windows的字体,那么会走Windows相关的路径;如果选择使用STB TrueType,就会走相应的路径。

总的来说,系统会根据是否使用Windows的字体或STB TrueType库,选择相应的代码路径来加载字体。这样用户可以灵活选择是否使用库,也为不同的字体渲染需求提供了可选方案。

TextOut:将文本绘制到 HDC

我们希望首先调用 TextOut 函数,并且在调用时需要传入设备上下文(DC)。此设备上下文将用于在位图的顶部绘制文本。在这里,不需要为文本设置偏移量,因为我们假设文本直接绘制在顶端。

在处理代码点时,我们遇到了一个问题,因为 Windows 的 TextOut 函数总是需要 UTF-16 编码的字符,而我们当前处理的字符可能是其他格式(比如 UTF-32)。因此,如果直接将 UTF-32 代码点转换为 UTF-16 可能会出现不完全正确的情况。严格来说,如果要进行转换,需要进行 UTF-32 到 UTF-16 的转换,而不是简单地将字符转换成所谓的"Cheese Point"(为了简化,暂时使用这种不完全正确的方式)。这种方法虽然不完美,但能够正确处理大多数 ANSI 字符和其他一些常见字符。

我们可以通过将这个"Cheese Point"作为单字符字符串传入 TextOutW 函数来绘制文本。这个过程看似能编译通过,但问题在于我们还没有获取到有效的设备上下文,因此在执行时会遇到缺少设备上下文的问题。

创建设备上下文

首先,我们需要创建一个设备上下文(DC)。为了简化,我们可以使用 CreateCompatibleDC(0) 来创建一个与屏幕兼容的设备上下文。这个方法看起来是最直接的选择,可以将当前的屏幕属性复制到新的设备上下文中。虽然有时也可以通过 GetDC(0) 来获取一个设备上下文,但在这个情况下,直接使用 CreateCompatibleDC(0) 应该是足够的,因此我们先尝试这种方法。

接下来,我们需要创建一个兼容的位图,并将其选择到设备上下文中。在 Windows 中,设备上下文(DC)是一个容器,我们可以将不同的图形对象(如位图、字体、画笔等)选择到其中。在这种情况下,我们会选择一个兼容的位图对象进入设备上下文,类似于设置当前活动的对象。这个过程类似于在窗口编程中选择对象到设备上下文中,以便这些对象在绘制时能被正确使用。

通过这种方式,我们实际上是在设置当前活动的位图、画笔或字体等对象,使得它们在后续的绘制操作中被使用。因此,我们的操作流程大致是先创建兼容的设备上下文,然后再创建兼容位图,最后将位图选择到设备上下文中,准备进行绘制。

CreateCompatibleBitmap

首先,创建一个兼容的位图时,我们使用已经创建的设备上下文(HDC),并设置位图的宽度和高度。为了确保总是有足够的空间来容纳所有可能绘制的字符,可以设定一个较大的尺寸,这样我们就不需要每次都计算精确的大小,确保足够的空间。

接下来,CreateCompatibleBitmap 函数会返回一个位图(HBITMAP),我们获取这个位图并将其选择到设备上下文中。这样,设备上下文就会使用这个位图进行绘制。

为了避免每次都重复执行这些操作,最好只初始化一次设备上下文和位图。我们可以使用静态变量来确保设备上下文只初始化一次。当设备上下文未初始化时,我们会创建它并进行相应的设置。

在创建设备上下文时,使用 CreateCompatibleDC 函数创建兼容的设备上下文。我们将位图选择到设备上下文后,就可以在这个设备上下文中绘制文本等内容。

总的来说,设备上下文和位图的创建是在一个条件判断中完成的,确保这些操作只在需要时执行一次。这样可以提高效率,并避免不必要的重复创建操作。在这个过程中,虽然我们在某种程度上"提取"了字体的相关信息,但还没有进行完全的字体提取操作。

还没有使用stb TrueType

目前我们还没有设置字体为 Arial,尽管之前使用的是 Arial 字体。此外,我们也不知道该设置多大的字体,存在很多不确定的情况。现在的问题是,我们还没有做这些基本的设置,所以在绘制文本时,字体的类型和大小都没有明确的定义,这导致了一些混乱。

为了确保文本能正确显示,首先需要设置正确的字体(例如 Arial),并且要决定字体的大小。如果没有正确设置字体和大小,绘制出来的文本可能不会按预期显示,因此需要进一步明确这些细节,才能确保绘制的文本符合预期效果。

这是一个雏形的东西α

目前,事情还没有完全顺利进行,而且我们还没有尝试提取任何内容。因此,接下来的工作量会很大。首先,需要明确一些基本的步骤。回想一下,之前的处理相对简单,只需要两步就完成了所有操作,提取的过程非常直接。那时,所有的代码只需将某个位图转换为我们所需的格式。

然而,Windows 的 API 不同,它的处理方式远比我们想象的复杂,甚至比以前还要麻烦。Windows 的 API 有很多限制和复杂的地方,这使得处理起来更加困难。

现在,所要做的工作是进行一系列的准备工作。首先,需要做的就是处理和准备好所有必需的操作和配置。

查找文本占用的空间

在 Windows 中绘制文本时,我们并不知道文本实际占用了多少空间。这与之前的简单情况不同,以前我们可以轻松地知道文本的宽度和高度,但在 Windows 中绘制文本时,无法直接获得这些信息。因此,如果需要知道文本的尺寸,就需要通过一些额外的函数来获取相关数据。

一个常用的方法是使用 GetTextMetrics 函数,这个函数可以帮助查询文本的相关信息,比如文本的高度、宽度等。另外,Windows API 还提供了一些其他的字体和文本相关的函数,能够获取字符串的尺寸等信息。

通过这些函数,可以查询到文本的实际尺寸,并根据这些信息来调整布局或做其他处理。因此,接下来需要使用这些 API 函数来获取文本的尺寸,确保能够准确地进行绘制和布局。

GetTextMetrics

GetTextMetrics 函数可以提供关于字体的一些信息,比如字体的高度等,这些信息对于后续的字体对齐等操作非常重要。因此,我们需要调用该函数来获取字体的相关度量数据。这个函数需要一个 TEXTMETRIC 结构体,我们可以通过这个结构体来获取填充好的字体度量信息,之后可以在做对齐等操作时使用。

另外,GetTextExtentPoint32 也是一个非常有用的函数,它可以返回文本的实际尺寸(宽度和高度)。这个函数会帮助我们确定绘制文本时需要的空间大小,从而实现文本的精确布局。通过这两个函数,我们可以获取文本的尺寸信息并用于后续的文本绘制与布局处理。

GetTextExtentPoint32

GetTextExtentPoint32 函数正是我们需要的,它可以计算指定字符串文本的宽度和高度,正符合我们的需求。使用这个函数时,我们将传入设备上下文、文本的起始位置以及文本的长度,它会填充一个结构体,包含文本的宽度和高度。

这个结构体包含了文本的宽度和高度信息,具体字段包括 size.cxsize.cy,分别表示宽度和高度。通过调用该函数,我们可以获得文本绘制后占据的区域,进而得知我们需要的空间大小。

一旦获取了文本的尺寸信息,就可以在后续的操作中利用这些数据,例如进行字体的提取或调整布局等。通过这些步骤,至少能够确保我们了解文本占用的空间大小,从而为进一步的操作做好准备。

在写字形之前清除背景:Patblt

为了确保能够正确绘制字体,我们首先需要清除绘制区域。每次绘制一个字符时,都会将其绘制到一个干净的位图上,因此必须先清空背景,避免之前绘制的内容影响新的绘制结果。通常,我们可以将背景清空为黑色,然后在其上绘制白色的文本或图形,这样可以确保字符的轮廓清晰且不会重叠。

在 Windows 中,可以使用 FillRect 函数来清空一个矩形区域,这个方法会填充指定区域的颜色。但是,由于 FillRect 使用画刷,因此它的性能相对较慢。为了提高效率,可以使用 PatBlt 函数,这个函数不会使用画刷,并且在某些情况下比 FillRect 更快,尽管现代系统的性能差异已经不再那么显著。

为了将背景设置为黑色,可以使用 PatBlt 函数,并指定一个特殊的光栅操作(Raster Operation),如 BLACKNESS,这会将指定区域填充为黑色。这样,在每次绘制文本之前,区域就已经被清空并准备好绘制新的内容。

完成这些操作后,接下来的步骤就是继续绘制文本或者执行其他操作。不过,尽管这些步骤看起来已经完成了清理和准备工作,但仍然有更多的细节需要处理。

SetTextColor

在使用 PatBlt 函数填充黑色背景后,需要确保文本的颜色设置正确。因为在调用 TextOut 函数时,默认情况下文本的颜色可能是黑色,这样就会导致黑色的文本绘制在黑色背景上,从而看不见文本,无法进行字体提取。

为了解决这个问题,可以使用 SetTextColor 函数来设置文本的颜色。通过该函数,我们可以指定文本的颜色,例如将文本颜色设置为白色。在 Windows 中,可以使用 RGB 宏来指定颜色值,例如使用 RGB(255, 255, 255) 来表示白色,这会将所有文本像素的颜色设置为白色。

这样,文本就会以白色显示在黑色背景上,确保可以清楚地看到文本内容,方便进行后续的字体提取操作。

尝试提取一些内容β

现在,文本已经以白色显示在黑色背景上,这意味着可以开始进行字体提取了。提取的过程与之前处理过的类似,步骤会完全相同。首先,我们需要分配内存,然后执行一些必要的操作,确保可以从背景中提取出字体。

接下来,按照预定的步骤,我们将继续处理并提取文本的图形或字符数据,确保字体的正确提取。

GetPixel:提取字形

为了提取字体信息,需要对原来的代码做一些修改。由于我们现在使用的是较慢的路径,或者说是"死像素路径",所以需要将之前用来获取 Alpha 通道的调用替换为 GetPixel 函数。GetPixel 可以让我们查询特定位置的像素值,传入设备上下文(HDC)以及像素的 X 和 Y 坐标,就可以获取该位置的像素颜色。

GetPixel 函数返回一个 COLORREF 类型的值,表示颜色。由于我们已经知道文本是用白色绘制的(即 RGB 值为 255, 255, 255),我们可以直接通过位操作提取颜色的红色、绿色或蓝色通道,进而获取 Alpha 通道的值。具体而言,可以通过将返回的 COLORREF0xFF 进行按位与操作来提取红色通道,并将其转换为 u8 类型,这样就能得到需要的 Alpha 值。

不过,需要注意的是,由于 GetPixel 是一个宽字符函数,可能存在函数类型不匹配的问题,因此需要根据具体情况进行适当的调整。

当前的代码框架已经大致形成了一个提取字体的过程,虽然字体尚未设置(因此不会得到 Arial 字体),但基本的提取步骤已经建立。

回顾之前的步骤

目前的过程大致上包括以下几个步骤:首先,创建一个兼容的设备上下文(DC),然后将一个兼容的位图选择进去。接着,确定绘制区域的大小,清除背景,使用白色绘制文本,接着逐像素地提取每个像素。这个方法虽然可以工作,但由于每次提取像素都需要调用操作系统的函数,因此非常慢,效率不高。

最终,提取的字体信息会被打包起来,整个过程完成后,就可以进行测试。测试步骤包括重新构建资产库,检查是否成功更新了资产文件。然后运行程序,查看是否有任何结果输出,尽管结果尚不确定。

整个流程是以最基础的方式实现的,虽然提取字体的方法效率较低,但它应该能在理论上正常工作,完成后会看到一些字体信息。

测试这个备用的字体实现γ

目前的结果并不理想,提取的字体显示为实心的,完全不是预期的效果。虽然从大小上看似乎没有严重错误,整体情况并不糟糕,但还是远未达到理想状态。因此,接下来需要分析和找出问题所在。

调试它

从当前情况来看,问题可能出在两个方面。首先,通过观察,发现每次获取的 Alpha 通道值都是 255(即白色),这显然不是预期的结果。尽管调用 GetTextExtentPoint32W 返回的字符大小看起来是正确的,但似乎在设置字体颜色或清除背景时存在问题。可能是 patblt 函数清除黑色背景时没有正常工作,或者 SetTextColor 函数没有成功将字体颜色设置为白色。通过进一步分析,可以确定问题很可能出在这些环节之一。

不需要调试;Casey 记得问题的原因

问题的根源已经明确了。通过运行一个小测试,设置文本颜色为黑色后,结果却显示所有字母都是反色的,显示为黑色背景上白色文字。这是因为 Windows 中的默认设置会导致这种情况发生。虽然这个行为看起来很不直观,但它是 Windows 图形处理中的一个常见 bug,通常开发者很容易忽视这一点。这种反转显示的问题一直存在,并且是开发过程中经常会遇到的一个难题。

SetBkmode:设置背景填充行为

在 Windows 中绘制文本时,有一个名为 SetBkMode 的函数,这个函数决定了绘制文本时背景是否保持原样,或者是否会用背景画刷填充文本周围的区域。如果背景设置为填充白色,那么绘制的文本周围区域也会被填充为白色,从而覆盖掉原本的背景色。

解决这个问题有两种选择:可以将 SetBkMode 设置为透明模式,这样文本会正常绘制,且不会改变背景颜色。问题的根源是,文本本身被绘制为白色,但背景区域也被白色覆盖,因此会出现反色现象,原本的黑色背景被填充为白色。

选择背景画刷,而不是调用 PatBlt

为了修复这个问题,可以通过选择一个黑色的画刷作为背景颜色来解决,而不是使用 PatBlt 或者设置透明模式。这样的方法会更加巧妙,因为它能够避免使用 PatBlt,并且通过选择一个背景画刷来实现黑色背景。这种方法避免了设置模式为透明,从而使得文本绘制时背景不会被覆盖为白色。

背景画刷可以通过设置 SetBkColor 来设置,如果选用一个黑色的画刷作为背景,就能够确保文本周围的背景区域保持为黑色,而不是被覆盖为白色。这个思路是相当有创意的,简单且有效。

SetDCBrushColor

在 Windows 中,默认的设备上下文(DC)背景画刷颜色是白色,这就是目前问题的根源。如果我们将其设置为黑色,背景就会变为黑色,从而避免文本被错误地绘制在白色背景上。通过更改背景画刷的颜色为黑色,应该可以解决当前的问题。这样操作之后,可以期待背景颜色变为黑色,并且文本将正确显示在这个黑色背景上。

测试一下

在 Windows GDI 中,背景颜色的设置没有直接的 set background brush color 这样的函数,而是通过其他方式来管理。尽管使用了 SetDCBrushColor 来设置画刷颜色,但这并没有达到预期的效果,因为它可能不直接影响文本绘制的背景。实际操作中,背景颜色的设置可能需要额外的调整,例如通过更改其他与背景相关的属性。

同时,在设置文本颜色时,虽然有 set text color 函数,但背景和前景颜色的处理似乎还需要更复杂的配置。也有可能是因为默认情况下,绘制文本时背景颜色会被覆盖,导致无法正确显示所需的效果。因此,背景刷的配置和文本颜色设置可能需要进一步调试和确认。

使用 SetBkColor 设置背景颜色

在研究 Windows GDI 的背景颜色设置时,发现 SetBkColor 函数似乎是解决问题的关键。根据文档,它设置了当前的背景颜色,并且不需要使用画刷,而是直接通过设置背景颜色来完成。这可能就是字体背景色问题的根本原因。

通过 SetBkColor 函数设置背景颜色后,可以避免使用画刷来填充区域,直接指定背景色即可。这样就能确保背景正确设置,而不会影响字体的绘制。所以,尝试使用 SetBkColor 来再次设置背景颜色,看看是否能够解决之前的显示问题。

成功!

通过使用 Windows GDI 函数,我们能够成功提取字体。尽管不同操作系统可能提供不同的选择和方法,但基本原理是类似的,只不过实现起来可能更加复杂。

目前的问题在于,无法选择所需的字体。在字体提取过程中,虽然可以实现基本的提取功能,但在选择特定字体时遇到了一些障碍。接下来,目标是能够选择所需的字体,而不仅仅是提取默认字体。这可能涉及到进一步的调整和方法,以便在提取过程中能够准确地指定目标字体。

CreateFont:选择字体

在 Windows 中创建字体和渲染字体有多种方式,其中一个常见的方法是使用 CreateFont 函数。尽管有这些方式,但创建字体的过程常常令人感到非常烦琐。特别是从文件中创建字体时,虽然可以加载字体文件,但并没有简单的方法直接从文件路径创建一个字体并用于渲染。

对于如何从文件中创建字体的问题,尽管 AddFontResource 函数允许将字体资源添加到系统中,但它并不是一个直接从文件创建字体供当前应用程序使用的简单方法。而且,即使添加了字体资源,它也会影响系统字体,而不仅仅是为当前应用程序加载字体。这意味着无法直接从文件创建一个字体,然后使用它进行渲染,而不需要将其永久添加到系统的字体库中。

因此,尽管 Windows 提供了一些方法来管理和加载字体资源,但直接从文件创建并使用字体进行渲染的方式并不直观,且没有明确简单的解决方案。

AddFontResourceEx:使字体对 Windows 可见

要从文件中加载并使用字体,AddFontResource 是最接近的方法,它允许将字体添加到 Windows 系统中,但这样做会将字体作为系统资源进行管理,而不仅仅是当前应用程序私有的资源。AddFontResource 会将字体文件(如 TTF 文件)添加到 Windows 中,并且可以通过传递 private 标志来确保该字体不会与其他应用共享,从而使其仅对当前应用程序可用。

然而,问题在于,即使通过 AddFontResource 将字体添加到系统中,也没有简单的方式直接从这些已添加的字体创建和使用一个字体对象。AddFontResource 函数会返回一个整数,表示添加的字体数量,但并不会返回字体的具体信息。因此,添加字体之后,无法直接从系统中提取并使用该字体,也无法指定某个字体来渲染文本。

至于如何使用这个已添加的字体,问题在于必须知道该字体的名称。字体添加到系统后,系统会处理字体管理,但没有简单的API允许我们直接从已添加的字体中创建字体对象,也没有明确的方式告诉系统我们要使用哪个字体进行文本渲染。因此,无法直接从文件加载并使用该字体,必须依赖于系统的字体管理机制,而这在应用程序中并不直观。

综上所述,虽然 AddFontResource 允许将字体添加到系统中,但要在应用中使用该字体并直接渲染文本,似乎没有直接的办法来实现,除非通过其他系统级别的方式间接使用字体。

我们需要指定字体名称

要在 Windows 中创建并使用字体,首先需要指定字体名称。虽然这在一些其他操作系统中可能不必要,但在 Windows 中,必须先将字体文件添加到系统的字体资源中,并确保知道字体的实际名称,而不是仅仅知道文件名。这是因为一旦字体被添加到系统中,Windows 只能根据字体的名称来识别并使用它,而不是通过文件名直接使用。

一旦字体被添加到系统中,使用 CreateFont 来创建一个字体对象时,需要提供一系列的参数,其中最重要的是字体名称。这些参数包括字体的高度、宽度、倾斜度、旋转角度、字体的粗细、是否斜体等。通过这些设置,Windows 会根据用户指定的条件来创建一个字体对象。

在设置字体时,通常使用像素值而不是点值来指定字体的大小,因为像素值更加直观。字体的粗细可以设置为"正常",不需要使用斜体或其他特殊样式。字符集可以设置为默认的字符集,因为使用 Unicode 字符集时,字符集参数的影响较小。其他设置,如输出精度、剪裁精度等,通常不需要特别关注,因为它们更多地影响渲染过程中的细节,而对于大部分应用来说,默认设置即可。

需要特别注意的是,当字体名称被确定时,字体创建的过程就完成了。在 Windows 中,AddFontResource 用于将字体文件添加到系统,但这只是第一步,之后需要通过字体名称来确保创建的字体对象正确地与系统中已加载的字体进行匹配。

简而言之,虽然添加字体资源到 Windows 系统是可以通过文件完成的,但要在应用中使用该字体,必须知道其字体名称,并在创建字体时传入该名称。虽然过程有些复杂,但这是 Windows 操作系统的字体管理方式。

我们还需要将字体标记为活动字体

创建字体只是第一步,但这还不足以让字体生效。我们需要确保使用该字体来绘制文本。为此,必须将字体对象设置为当前设备上下文(DC)中使用的对象,就像之前处理位图时一样。通过选择该字体对象到 DC 中,可以将其设置为活动字体,从而确保绘制时使用的是我们刚刚创建的字体。

在设置好字体后,系统会将该字体作为活动字体应用,这时绘制出来的文本应该符合预期的字体样式。如果一切顺利,显示的文本应该会呈现出所选字体的效果,比如 Arial 字体。

使用更易识别的字体测试渲染器

可以尝试将字体设置为更容易辨识的字体,比如 "Courier New"。这样如果重新运行资产处理程序并查看字体的输出,应该能看到明显不同的效果。不过,目前看起来字体比较细,可能是因为生成的字体过大或过小,这也让字体的显示效果不如预期。因此,还需要进一步调整或测试,找出最佳的字体大小和粗细,以确保其外观符合需求。

暂时回到 STB 字体渲染

可以尝试使用 SDB 字体库,看看效果如何。通过运行资产构建并重新启动游戏,可以看到效果明显更好,字体显示更加清晰。这样看起来,SDB 字体库能够更精确地获取文本的矩形区域,而不是仅仅返回文本的像素。这也意味着,SDB 库在计算文本的扩展区域时可能更加准确,从而使得字体显示更加精致。可以通过一些测试,例如在文本周围添加边框,来进一步确认这个假设。

退回到实心 alpha 以检查边界框

为了处理字体的透明度问题,可以通过修改 Alpha 值来强制设置字体为不透明。可以在设置 Alpha 时将其值设置为 0xFF,这代表完全不透明,然后将颜色设为灰色。通过这种方式,确保了字体的透明度是固态的,从而可以准确地显示字体的位图。

接着,使用 GetTextExtentPoint 函数来获取字体的矩形范围时,发现 Windows 提供的字体比 SDB 库的字体显示得更宽松。具体来说,SDB 库能够为字体提供一个紧密的边界框,没有多余的空白区域,而 Windows 字体则会在字符的上下留有空白区域。这可能是因为 GetTextExtentPoint 函数在获取字体的范围时,仅考虑了水平方向,而没有处理垂直方向的空间。

为了进一步确认这个问题,可以在游戏中临时调整粒子的大小,看看空白区域的表现。通过调整粒子系统的大小,可以更明显地看到字体显示时的空白区域。如果是垂直方向的间距问题,那么解决方法可能是调整 GetTextExtentPoint 以处理这些垂直空间,或者找到一种方法使 Windows 更加精确地计算文本的边界。

另外,也可以考虑尝试其他字体扩展方法,或者查看 GetTextExtentPoint 函数的文档,探索是否有其他的扩展可以解决这个问题。

// truetype

//windows

扫描位图手动获取字形的边界框

首先,为了确定字体的边界区域,可以通过手动计算最小和最大坐标来精确绑定区域。步骤是设置初始的 minXminY 为一个非常大的值,接着在遍历每一个像素时,如果像素值不为零(即不是黑色像素),则表明该像素在有效范围内。这时,更新 minXmaxXminYmaxY 值,确保我们能找到所有有效像素的最小和最大坐标。

具体操作中,当遇到每一个像素时,如果它的 Y 值小于当前 minY,就更新 minY;如果它的 X 值大于当前 maxX,就更新 maxX,以此类推,逐步找出字符的实际边界范围。完成这一步后,得到了字符的矩形边界,可以用 maxX - minX 计算宽度,用 maxY - minY 计算高度。但需要注意的是,这个计算会遇到"边界问题":如果最小值和最大值相同(比如只有一个像素被填充),那么宽度应该是 1,而不是 0。所以,实际的宽度应该是 maxX - minX + 1,以确保即使只有一个像素也能得到正确的宽度。

完成这部分工作后,通过这些边界值计算出的宽度和高度会更为准确,能够避免不必要的空白区域。计算后的区域应该紧密围绕有效字符,并能够反映出更为精确的显示效果。

然而,虽然这样计算出了正确的边界,显示效果中仍然出现了锯齿状的边缘,这可能是因为 Windows 没有进行抗锯齿处理。在这种情况下,可以进一步检查和调整,以确保字体渲染能够平滑显示,并解决可能的抗锯齿设置问题。

总结来说,虽然我们通过计算最小最大坐标正确地获得了字符的显示区域,但仍需要进一步优化渲染效果,特别是在抗锯齿处理上。

添加一个像素的空白边框

在处理字体时,除了计算出字符的最小和最大坐标,还需要考虑添加一个1像素的边框。这个边框对于填充例程来说是必要的,因此需要在计算最小坐标时,将 minX 设置为比正常值小1,即将字符的边界稍微扩大,以便为字体周围添加一个1像素的空白区域。这种做法可以确保在字符的四周留出足够的空间,从而满足填充的需求。

至于抗锯齿效果没有正常工作的原因,目前还不清楚。可能需要在设备上下文中做一些特别的设置,例如启用抗锯齿功能或使用透明背景模式(BK_MODE 设置为透明)。虽然在当前的实现中没有设置这些选项,但这可能是导致抗锯齿没有生效的原因。除此之外,可能还需要检查是否需要为字体渲染创建自己的背景位图,或者在其他地方可能存在问题。这个问题可以在后续的调试中深入探讨,暂时可以留到以后再解决。

总体来说,当前的处理已经比较完善,添加了必要的边框,其他问题如抗锯齿效果的调试可以留待之后的时间解决。

字体可以有轮廓吗?

关于字体是否可以添加轮廓的问题,通常来说,字体本身并不自带轮廓,但确实可以通过一些方法为字体添加轮廓。具体来说,如果我们希望给一个本身没有轮廓的字体添加外轮廓,可以采取如下方法:

  1. 绘制轮廓:可以通过代码将字体的边缘或者文字周围的轮廓单独绘制出来,这通常需要在渲染文本时额外绘制一层。这层可以是一个带有颜色的边框,通常比文本颜色稍微加粗或者加深。

  2. 使用图形库:某些图形库(如Windows的GDI+或OpenGL)允许我们在绘制文本时设置"描边"效果,即为字体添加轮廓。这样,尽管原始字体本身没有轮廓,显示时就可以有一个带有轮廓的效果。

  3. 字体编辑工具:可以通过字体设计软件或者工具,在字体的设计阶段为字体添加轮廓。这会生成一个新的字体文件,原本没有轮廓的字体就变得有了轮廓。

总的来说,虽然字体本身没有轮廓,但通过适当的技术和方法,可以为任何字体添加外部轮廓效果,这样在显示时就能达到想要的视觉效果。

你说的"背后位图"是什么意思?

"Backing bitmap"指的是设备上下文(DC)在绘制图形时所使用的实际位图。设备上下文通常会与一个位图关联,这个位图就是设备上下文绘制时的目标。在这里,未显式创建位图,而是直接指定一个与设备上下文兼容的位图。这个位图就是所谓的"backing bitmap",即为绘制提供基础的位图。

在绘制字体时,如果字体的抗锯齿效果没有显示出来,可能的原因之一是没有正确创建合适的位图。设备上下文本身可以选择一个位图进行绘制,但是否需要手动创建特定的位图,确保正确的抗锯齿效果,则不太清楚。可以尝试几种方法来诊断问题,包括:

  1. 设置透明背景模式:尝试将设备上下文的背景模式(BK mode)设置为透明,以查看是否能够改善抗锯齿效果。

  2. 检查字体缩放:可能字体在缩小过程中失去了抗锯齿效果。测试时,可以通过放大字体来确认是否有抗锯齿效果。

  3. 检查是否是位图问题:也有可能问题出在"backing bitmap"上,如果位图的创建方式不正确,可能导致无法正确实现抗锯齿效果。GDI(图形设备接口)渲染机制有时比较复杂和不确定,因此可能需要创建一个特定类型的位图,才能正确启用抗锯齿。

总的来说,解决抗锯齿问题可能涉及到调整设备上下文的设置、创建适合的位图或使用其他方法来确保字体显示时能够正确呈现抗锯齿效果。

我喜欢 stb_truetype,它比 Windows API 版本更简单且更好

使用truetype的版本相较于Windows API版本更为简便,且效果上可能更好,这一点并不令人惊讶。值得注意的是,truetype提供的接口可能更直观和易用,因此它在一些情况下可能会被认为是更合适的选择。此时可以在一个投票调查(Straw Poll)中进行选择,以便了解大家的偏好。

Windows 字体支持下标和上标吗?因此会有过多的边界吗?另外,Windows 是否只是将抗锯齿存储在 alpha 值中?看到像你这样的专业人士在与这个 API 搏斗后,我明白了你面临的压力,昨天看过 STB 后

讨论中提到了Windows的字体API和其在反锯齿、上标和下标支持方面的一些问题。首先,提到Windows字体API的反锯齿效果似乎只通过Alpha值来存储,这可能是导致显示效果不理想的原因之一。尤其是在一些API调用中,反锯齿的处理需要额外的步骤和理解,很多开发者在面对这些API时会遇到困难。

对于Windows的旧版图形API(如GDI),与更新的图形库(如Direct2D和WPF)相比,虽然Direct2D有一定的改进,但仍然被认为在某些方面表现不佳。实际上,Windows的图形处理API随着时间推移变得越来越复杂且难以使用。

另外,关于上标和下标的支持,通常情况下,Windows并不直接提供对上标和下标的专门支持。开发者往往通过调整字体大小来模拟这些效果,或者通过调整行高来为这些效果留出空间。字体的行高通常是通过GetTextExtentPoint函数来计算的,而这个函数通常返回的是行高,而非具体的上标或下标处理。

进一步澄清,我们能否创建带有字符轮廓的字体位图,轮廓可以单独上色?

关于字体和图形渲染的技术问题,特别是与TrueType字体格式的限制相关。首先,关于是否可以创建不带字符边框的平面位图并将其单独上色的问题,回答是可以的,前提是字体本身支持这种功能。然而,TrueType字体格式并不直接支持字符边框或轮廓,它通常通过将轮廓填充为形状并使内部保持空心来模拟轮廓效果。因此,无法直接在TrueType字体中实现这一点。

如果需要支持轮廓的字体,可能需要使用其他字体文件格式(例如,OpenType或PostScript字体)。不过,系统本身对这些字体格式并不在乎,因此即使使用其他格式的字体,系统也能够处理位图形式的字符,可以为每个字符绘制边框,进行全彩渲染等。

然而,Windows本身的字体系统并不直接支持字符的轮廓,因此不能轻易在字体中实现这一效果。

哪个更糟糕:GDI 还是 DirectInput8?

讨论中提到GDI和DirectInput 8的对比。GDI和DirectInput 8在功能上有一些重叠,具体选择哪个取决于需求,但一般认为DirectInput 8更好一些。因为DirectInput 8提供了更好的支持,尤其是在处理输入设备方面,如更好的兼容性和更细致的控制。因此,相比GDI,DirectInput 8在处理输入和相关任务时可能更加稳定和高效。

今天是为什么有时候使用库是可以的一个好例子吗?

使用库的关键在于对问题的理解。如果对问题有足够的了解,并且知道库如何运作,以及它不会给项目带来问题,那么使用库是完全可以接受的。通常,使用库的问题出在对库的理解不足,导致在维护库、处理库中的bug或移植库到其他平台时出现问题。此外,很多时候,使用库的人对库的性能、限制或优势不了解,因此可能在某些情况下遇到性能瓶颈或其他难题。

然而,当你对问题有深入了解时,通常会发现大多数库并不比自己实现要好,反而可能带来麻烦。很多库并没有解决实际问题,使用它们可能反而会浪费时间。因此,只有在真正了解库的运作方式及其优缺点时,使用库才是合适的。

例如,STB库就是一个例外,它们非常好,能够有效地节省时间,并且使用这些库时不会遇到太多麻烦,因此它们是一个值得使用的库。而一些其他库,尤其是功能不明确或过于复杂的库,往往无法带来实际的好处。因此,使用库是否合适,取决于对该库领域的熟悉程度,以及对库的构造和工作原理的了解。

在实际应用中,选择不使用库的原因通常不是因为没有好的库可用,而是为了让学习者能更深入地理解每个部分的实现过程。在某些情况下,限制库的使用可以帮助开发者更好地理解和掌握底层的工作原理。在游戏开发中,避免过多依赖库,尤其是为了展示如何从零开始编写游戏的核心部分,这样可以更好地教育学习者,而不是直接使用现成的库来完成工作。

DirectWrite(或 ClearType)能帮助抗锯齿吗?

通过启用 ClearType 和一些额外的设置,确实可以在引擎中启用抗锯齿功能。抗锯齿工作原理是通过处理字体的渲染路径来改善字体的显示效果,尤其是在低分辨率或缩小的情况下。之前已经尝试过这样的设置,并且它是有效的。如果遇到问题,可能只是缺少了某些必要的配置或设置,因此可以检查是否正确启用了相关选项。

通常,启用 ClearType 可以改善文本的清晰度,特别是在显示屏上。通过调整引擎的设置,应该能够获得更平滑的字体边缘,从而避免出现明显的锯齿。

如果你已经在做这个,请忽略这个问题,但为什么不直接将单色位图作为资产文件的一部分,而不是更大的 RGBA 位图?

将单色位图(mono bitmap)作为资产文件的一部分,而不是使用更大的RGB位图,确实是一个空间优化策略。但这种优化带来的复杂性需要考虑。首先,处理单色位图会增加代码的复杂度,因为每次加载位图时都需要判断它是单色位图还是常规的RGB位图。这将要求渲染器添加额外的代码来处理单色位图,或者在加载时进行转换,这会增加加载时间,导致加载代码变慢。

因此,做出这个优化决策时需要权衡,特别是考虑到增加的代码复杂度、可能变慢的加载时间和渲染器的复杂性,所带来的收益仅仅是稍微减小的资产文件大小。在这种情况下,可能没有太大的意义。考虑到字体是唯一可能使用单色位图的地方,甚至可能有其他像粒子系统这样的少数应用场景,这种优化可能并不值得。

而且,如果通过LZ压缩器处理代码,压缩器通常会自动将重复的544个相同值合并,从而把文件大小缩小,相当于自动将它们压缩成单色位图。所以,这种优化方案带来的文件大小压缩效果在使用压缩器的情况下,可能已经能够达成目标,且不会增加额外的复杂度。

你有多少次后悔使用一个 API/库并开始写自己的实现?当你处于快速开发节奏时,可能有时自己实现更好,尤其是当你对问题有足够了解时

在快速开发的环境中,使用第三方API或库确实有时可能带来一些好处,尤其是在对问题的理解足够深入的情况下。然而,通常情况下,对于我来说,自己编写实现比依赖第三方库要好。我的编程经历告诉我,几乎每次使用第三方工具、软件或库,最终都会感到后悔,除非是STB库,它是为数不多的我没有后悔使用的例子。大部分第三方工具让我觉得它们质量不高,每次更新到新版本时,总会有不兼容、变慢甚至出现崩溃的情况,导致我对它们产生了强烈的厌恶。

因此,我的做法是尽量避免引入新的第三方库,尽量将现有的第三方依赖替换为自己编写的代码,唯一例外的就是STB库,因为我对它的使用感到非常满意。总的来说,我的原则是:对于现有的工具和库,我会继续使用,但不会再增加新的第三方依赖,并且我正在逐步替换掉我使用的所有第三方库,除非它们像STB那样质量非常好。

至于推荐哪种GUI工具包,实际上并没有明确的答案,因为不同的需求和平台会有不同的适用工具。如果需要跨平台支持,可以考虑Qt或者SDL,如果只是为Windows开发,Windows自带的WPF或WinForms也是可选的,但这些工具包的选择依然取决于项目的具体需求。

你推荐使用哪个 GUI 工具包?

对于图形用户界面(GUI)工具包的选择,并没有太多的经验。虽然我知道有一个人,记得他可能来自Media Molecule,最近开源了一个即时模式(Immediate Mode)图形用户界面库。据说这个库的设计思想是参考了我之前提出的即时模式图形用户界面的方式,而且它得到了不少人的好评。虽然我个人对这个库没有太多的经验,也没有特别强的偏好,但既然大家似乎很喜欢它,或许它是个不错的选择。

总体来说,对于GUI工具包的选择,每个人的需求不同,因此具体选什么工具包还是要看项目的具体情况。不过,这个开源的即时模式GUI库看起来值得关注。

使用 CLEARTYPE_QUALITY 而不是 ANTIALIASED_QUALITY 会有区别吗?

ClearType质量与抗锯齿的效果有所不同。ClearType实际上是通过调整颜色通道来改善文本在LCD面板上的显示效果。它特别适用于LCD屏幕,通过细化文本的颜色层次,使得文本边缘更加平滑,从而提高阅读舒适度。但是,ClearType并不是我们所需要的,它处理的是颜色层次,而非单纯的抗锯齿技术。

抗锯齿是通过在图形的边缘添加半透明像素来减少锯齿感,而ClearType更侧重于通过调整颜色来使文本显示得更清晰和柔和。对于我们来说,抗锯齿的效果是为了让字体在图像中显示得更平滑,而ClearType则是针对特定类型的屏幕优化,主要用于提升文字的可读性。

Blackboard:LCD 像素元素

LCD面板通常由多个像素元素组成,每个像素可能包含红色、绿色和蓝色的子像素条形,这种排列方式可以提供比单个像素更精细的颜色精度。通过这种方式,当两个像素并排放置时,可以在颜色通道中实现亚像素级的精度。举例来说,如果想渲染一个字体并精确对齐,可以通过在像素之间加入蓝色边缘,并填充红绿蓝颜色,从而获得比实际像素数量更高的水平分辨率,也就是说,它可以达到3倍的分辨率。

然而,对于我们的情况,这种ClearType效果并不适用,因为在进行位图缩放时,任何这种色带或边缘效果都会显得特别突出,像刺眼的标记一样。因此,在我们的渲染引擎中,绝对不希望ClearType效果发生,这会造成严重问题,必须避免。

相关推荐
喃寻~5 分钟前
java学习总结(四)MyBatis多表
学习
栀子清茶28 分钟前
Towards Universal Soccer Video Understanding——论文学习(足球类)
论文阅读·人工智能·深度学习·学习·算法·计算机视觉·论文笔记
F_lander1 小时前
蓝桥杯学习-08序列二分
学习·职场和发展·蓝桥杯
2401_872487881 小时前
网络安全之前端学习(HTML篇)
学习·html
qq_589568102 小时前
java学习笔记2
java·笔记·学习
知识分享小能手2 小时前
CSS3学习教程,从入门到精通,CSS3 文字样式语法知识点及案例代码(7)
前端·javascript·学习·html·css3·html5·前端开发工程师
小呀小萝卜儿2 小时前
2025-03-14 学习记录--C/C++-DS-头结点和头指针的区别
c语言·学习
Leo来编程2 小时前
Python学习第十九天
python·学习
云上艺旅4 小时前
K8S学习之基础二十九:K8S中的secret
学习·云原生·容器·kubernetes
云上艺旅4 小时前
K8S学习之基础三十一:k8s中RBAC 的核心概念
java·学习·云原生·kubernetes