Spritesheet 以编程方式切割:最佳实践

Posted

技术标签:

【中文标题】Spritesheet 以编程方式切割:最佳实践【英文标题】:Spritesheet programmatically cutting: best practices 【发布时间】:2011-11-12 13:13:15 【问题描述】:

我有一个由 42 帧组成的大精灵表 (3808x1632)。 我会用这些帧呈现一个动画,然后我使用一个线程来加载一个包含所有帧的位图数组,并带有一个等待结束的初始屏幕。 我没有使用 SurfaceView(和画布的绘制功能),我只是在我的主布局中的 ImageView 中逐帧加载。 我的方法类似于Loading a large number of images from a spritesheet 完成实际上需要将近 15 秒,不能接受。

我使用这种功能:

for (int i=0; i<TotalFramesTeapotBG; i++) 
            xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG; 
            yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
            mVectorTeapotBG.add(Bitmap.createBitmap(framesBitmapTeapotBG, xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG));
        

framesBitmapTeapotBG 是大精灵表。 更深入地看,我在logcat中读到createBitmap函数需要很多时间,可能是因为spritesheet太大了。 我找到了可以在大精灵表上创建一个窗口的地方,使用 rect 函数和画布,创建要加载到数组中的小位图,但不是很清楚。我说的是那个帖子:cut the portion of bitmap

我的问题是:如何加快精灵表的剪切速度?

编辑: 我正在尝试使用这种方法,但我看不到最终动画:

    for (int i=0; i<TotalFramesTeapotBG; i++) 
        xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG; 
        yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
        Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bmFrame); 
        Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG); 
        Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);  
        c.drawBitmap(framesBitmapTeapotBG, src, dst, null);         
        mVectorTeapotBG.add(bmFrame);
    

可能是位图 bmFrame 没有正确管理。

【问题讨论】:

【参考方案1】:

简短的回答是更好的内存管理。

您正在加载的精灵表很大,然后您将其复制成一堆小位图。假设精灵表不能更小,我建议采用以下两种方法之一:

    使用单独的位图。这将减少内存副本以及 Dalvik 必须增长堆的次数。但是,这些好处可能会受到需要从文件系统中加载许多图像而不是仅一个图像的限制。普通计算机会出现这种情况,但 android 系统可能会得到不同的结果,因为它们会耗尽闪存。 直接从 sprite 表中进行 Blit。 绘制时,只需使用 Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) 之类的东西直接从 sprite 表中绘制。这会将您的文件负载减少到一个可能只需要在您的活动生命周期中发生一次的大型分配。

我认为第二个选项可能是两者中更好的一个,因为它在内存系统上更容易,并且对 GC 的工作量更少。

【讨论】:

好吧,事实上,在我看来,您的第二个选择更好。我正在尝试这样做,我编辑了我的第一个问题。【参考方案2】:

感谢stevehb的建议,我终于明白了:

for (int i = 0; i < TotalFramesTeapotBG; i++) 
    xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG; 
    yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
    Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bmFrame);  
    Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, xStartTeapotBG+frameWidthTeapotBG, yStartTeapotBG+frameHeightTeapotBG); 
    Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);  
    c.drawBitmap(framesBitmapTeapotBG, src, dst, null);         
    mVectorTeapotBG.add(bmFrame);

计算时间下降得惊人! :)

【讨论】:

【参考方案3】:

使用LevelListDrawable。将精灵切割成单独的帧并将它们放在您的可绘制资源目录中。以编程方式或通过xml based level-list drawable 创建您的drawable。然后使用ImageView.setImageLevel() 选择您的框架。

【讨论】:

你的方法很清楚,我之前已经在其他应用程序中使用过,但是对于一个有 42 帧的 spritesheet,我一个人制作所有这些真的很无聊。我正在寻找一个从 spritesheet 开始并自动完成所有其余部分的自动过程。 只需像以前一样以编程方式将其切碎,然后将位图输出到文件中。查看 [Bitmap.compress()](developer.android.com/reference/android/graphics/…, int, java.io.OutputStream))。【参考方案4】:

我使用基于行和列的切片方法。但是,您的精灵表相当大。您必须认为您正在将整张纸放入记忆中。 3808x1632x4 是内存中图像的大小。

无论如何,我要做的是拍摄一张图像(假设是 128x128),然后告诉它 Sprite(bitmap, 4, 2) 构造函数中有 4 列和 2 行。然后你可以根据它进行切片和切块。 bitmap.getWidth() / 4 等...非常简单的东西。但是,如果您想做一些真正的事情,请使用 OpenGL 并使用纹理。

哦,我也忘了提到有一些 onDraw 需要发生。基本上,您保留一个索引计数器并从位图中切出一个矩形,然后将其从源矩形绘制到画布上的目标矩形。

【讨论】:

以上是关于Spritesheet 以编程方式切割:最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中以编程方式移动 NavigationView 的最佳实践是啥

以正确方向以编程方式显示 UIView 的最佳实践

JS编程最佳实践

多个小型 spritesheet 或一个巨大的 spritesheet 以提高性能? ---(java游戏开发)[关闭]

在 iOS 中保存访问令牌的最佳实践

ios 轮换最佳实践