【问题标题】:Need to crop thousands of images / sprites while preserving midpoint需要在保留中点的同时裁剪数千张图像/精灵
【发布时间】:2016-02-15 17:45:22
【问题描述】:

我有数千个从 Maya 中保存出来的角色精灵(尽管它们实际上只是具有透明度的 PNG 文件)。

这些是角色动画,几乎每个都是 12 帧。每个动画都从 8 个基本方向中的每一个进行渲染。

在每个动画中,角色已经在帧中居中(这是从 Maya 导出的方式);但是,每个图像周围都有一堆白色/透明空间。

我需要从外部水平批量裁剪这些精灵 -> 以保留角色在帧中的中点。原因是如果它没有被保留,当游戏从一个动画切换到下一个动画时,角色会出现移动或跳跃。因此,重要的是角色在所有动画的帧中水平居中(使用他们身体的中点)。

此外...理想情况下,角色始终位于或非常靠近精灵的底部边缘。

对于致命一击...由于游戏引擎的原因,最终裁剪后的图像需要是偶数,或者可以被 2 整除。

有没有办法至少部分或全部自动化?或者是否有人可以推荐任何程序来帮助实现这一点?免费或付费。

【问题讨论】:

  • 如果您选择的脚本语言或驱动程序提供适当的命令行,imagemagick 很有可能会进行处理。
  • @Chris 这是我的第一个想法,我之前使用过 imagemagick,我只是不完全确定如何达到预期的效果。我还需要确保不要过度裁剪序列中的任何图像。
  • 如果这些生成的图像具有有效的空背景,应该很容易找到其中的字符 - 只需扫描非背景像素。但如果角色打算移动,定义静态中心可能与范围中心不同。
  • 如果我们能看到一些会有所帮助...
  • 由于种种原因,我无法展示实际的精灵,但是this link shows exactly the same concept。整个过程几乎就像从图像序列中找到最小的边界矩形一样,但不同之处在于保留了水平对称性。在我的示例中:即使它可以裁剪掉右侧的负空间,我也不想这样做,因为它会使圆偏离中心,我需要保留圆的水平中点/对称性.

标签: image sprite crop maya 2d-games


【解决方案1】:

我不知道基于 Python 的答案是否适合您,但这是我得到的,通过一些工作,您可以将此脚本复制并粘贴到您在 Maya 中的脚本编辑器或在您的 Python 解释器中执行它。

此脚本需要安装 PIL 库。

要在 Maya 上安装它,您可以点击此链接 Mistermatti's blog。解压缩 file 并将 PIL 文件夹粘贴到您的 Maya 安装中:Maya20XX\Python\Lib\site-packages 这适用于 Maya2014,尚未在其他版本上测试过。

from PIL import Image
import time
start_time = time.time()

#parses the image to find the first pixel on the x axis which is not blank
def parseXImage(pixelMap, resWidth, resHeight):
    for x in range(0, int(resWidth/2)):
        for y in range(0, int(resHeight/2)):
            if (pixelMap[x,y] != (0, 0, 0, 0)) or \
                (pixelMap[x,resHeight-y-1] != (0, 0, 0, 0)) or \
                (pixelMap[resWidth-x-1,y] != (0, 0, 0, 0)) or \
                (pixelMap[resWidth-x-1,resHeight-y-1] != (0, 0, 0, 0)):
                return x
    return None

#parses the image to find the first pixel on the y axis which is not blank
def parseYImage(pixelMap, resWidth, resHeight):
    topFound = False
    bottomFound = False
    yTop = None
    yBottom = None
    for y in range(0, int(resHeight/2)):
        for x in range(0, int(resWidth/2)):
            if not topFound:
                if (pixelMap[x,y] != (0, 0, 0, 0)) or \
                    (pixelMap[resWidth-x-1,y] != (0, 0, 0, 0)):
                    yTop = y
                    topFound = True
            if not bottomFound:
                if (pixelMap[x,resHeight-y-1] != (0, 0, 0, 0)) or \
                    (pixelMap[resWidth-x-1,resHeight-y-1] != (0, 0, 0, 0)):
                    yBottom = y
                    bottomFound = True
    return yTop, yBottom

imageList = [r"Path\To\Image\Mod_Turn.001.png",
    r"Path\To\Image\Mod_Turn.002.png",
    r"Path\To\Image\Mod_Turn.003.png",
    r"Path\To\Image\Mod_Turn.004.png",
    r"Path\To\Image\Mod_Turn.005.png"]

#get images resolution
imagePil = Image.open(imageList[0]) 
resWidth, resHeight = imagePil.size #get the resolution of the first image in the list
imagePil.close()

valueWidthList = [] 
valueHeightTopList = []
valueHeightBottomList = []
for imageListIndex, imagePath in enumerate(imageList):
    imagePil = Image.open(imagePath)
    pixelMap = imagePil.load()

    xValue = parseXImage(pixelMap, resWidth, resHeight) #now parse the image to find x
    yTopValue, yBottomValue = parseYImage(pixelMap, resWidth, resHeight) #now parse the image to find y
    valueWidthList.append(xValue) #Store it
    valueHeightTopList.append(yTopValue) #Store it
    valueHeightBottomList.append(yBottomValue) #Store it
    imagePil.close()

xValueToCrop = min(valueWidthList)  #Get the minimal value of X crop among all the entries
yTopValueToCrop = min(valueHeightTopList) #Get the minimal value of Y crop among all the entries
yBottomValueToCrop = min(valueHeightBottomList) #Get the minimal value of Y crop among all the entries
#This is the crop square we will use
cropSquare = (xValueToCrop, yTopValueToCrop, resWidth-xValueToCrop, resHeight-yBottomValueToCrop  )

for imagePath in imageList:
    imagePil = Image.open(imagePath)
    #Replace Turn by something in the name of your image
    imagePil.crop(cropSquare).save(imagePath.replace("Turn", "Turn_Cropped")) #Crop dans save the image
    imagePil.close()

print("--- %s seconds ---" % (time.time() - start_time))

您需要在脚本的最后使用自己的图像路径和"Turn", "Turn_Cropped" 更改 imageList dict。

仅供参考,以 720p 处理 300 张图像大约需要 250 秒。这可以改进。

【讨论】:

  • 非常感谢@DrHaze——我会仔细研究一下,因为它可能是一个可行的解决方案。非常感谢!
【解决方案2】:

也许试试 TexturePacker https://www.codeandweb.com/texturepacker

我以前在处理精灵时使用过它,发现它非常好。我相信它也有命令行功能,虽然我自己没有使用过。而且它是跨平台的。

【讨论】:

  • 我以前用过TexturePacker,但我不知道如何简单地裁剪一系列精灵。我们不(也不能使用)精灵表。否则,TexturePacker 是我最初的想法之一......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 2012-06-03
  • 1970-01-01
相关资源
最近更新 更多