2012-11-27 21 views
3

給定一個精靈表是這樣的:精靈表單檢測個別的Sprite界自動

Sprite Sheet Example

我想寫入的算法,可以循環通過所述像素數據,並確定各分立子畫面的邊界矩形。如果我們假設對於每個像素X,Y我可以拉真(像素是不是完全透明)或假(像素是完全透明),我將如何去自動生成每個精靈的邊界矩形?

生成的數據應該是具有{x,y,寬度,高度}的矩形對象的數組。

這裏是相同的圖像,但註明在淡藍色的前四個精靈的界限:

Sprite Sheet With Bounds

誰能給一個一步一步如何如上所述檢測這些界限?

+0

難道精靈總是完全連接? –

+0

並非總是如此,但重要的一點是它們之間會存在一條直線的透明度。 –

回答

2

這個怎麼樣?唯一的缺點是你需要一個可寫的圖像版本來標記訪問的像素,否則floodfill永遠不會終止。

Process each* scan line in turn 
    For each scanline, walk from left to right, until you find a non-transparent pixel P. 
    If the location of P is already inside a known bounded box 
     Continue to the right of the bounded box 
    Else 
     BBox = ExploreBoundedBox(P) 
     Add BBox to the collection of known bounded boxes 

Function ExploreBoundedBox(pStart) 
    Q = new Queue(pStart) 
    B = new BoundingBox(pStart) 

    While Q is not empty 
    Dequeue the front element as P 
    Expand B to include P 

    For each of the four neighbouring pixels N 
     If N is not transparent and N is not marked 
     Mark N 
     Enqueue N at the back of Q 

    return B 

你不需要處理每一條掃描線,你可以每隔10或30掃描一次。只要它不超過最小精靈高度。

+1

很好的答案。我有兩條評論:1.如果您沒有可寫的圖像版本,則可以使用Set來跟蹤標記的像素。 2.當找到'ExploreBoundedBox'中的相鄰像素時,獲得_eight_相鄰像素(即對角線)可能會更好。看第一排的松樹;其最右邊的像素僅與主體對角連接。 – Kevin

+0

謝謝@Astrotrain和凱文。我要實現這個並讓你知道結果! –

+0

@凱文:你說得對,好點。但是我建議使用Hashtable而不是Set,因爲它比大集合的性能更好(並且精靈中的像素數量非常大)。在圖像內部繪製仍然是最快的,所以如果這是一個選項... – Astrotrain

0

連接實現對Python和枕頭:

網址:https://gist.github.com/tuaplicacionpropia/f5bd6b0f69a11141767387eb789f5093

URL

#!/usr/bin/env python 
#coding:utf-8 

from __future__ import print_function 
from PIL import Image 

class Sprite: 
    def __init__(self): 
    self.start_x = -1 
    self.start_y = -1 
    self.end_x = -1 
    self.end_y = -1 

    def expand (self, point): 
    if (self.start_x < 0 and self.start_y < 0 and self.end_x < 0 and self.end_y < 0): 
     self.start_x = point[0] 
     self.start_y = point[1] 
     self.end_x = point[0] 
     self.end_y = point[1] 
    else: 
     if (point[0] < self.start_x): 
     self.start_x = point[0] 
     if (point[0] > self.end_x): 
     self.end_x = point[0] 
     if (point[1] < self.start_y): 
     self.start_y = point[1] 
     if (point[1] > self.end_y): 
     self.end_y = point[1] 

    def belongs (self, point): 
    result = False 
    result = True 
    result = result and point[0] >= self.start_x and point[0] <= self.end_x 
    result = result and point[1] >= self.start_y and point[1] <= self.end_y 
    return result 

    def __str__(self): 
    result = "" 
    result = result + "(" 
    result = result + str(self.start_x) 
    result = result + ", " 
    result = result + str(self.start_y) 
    result = result + ", " 
    result = result + str(self.end_x) 
    result = result + ", " 
    result = result + str(self.end_y) 
    result = result + ")" 
    return result 

def loadSprite (pos, sprites): 
    result = None 
    for sprite in sprites: 
    if sprite.belongs(pos): 
     result = sprite 
     break 
    return result 


def exploreBoundedBox (pStart, img): 
    result = None 
    q = [] 
    q.append(pStart) 
    result = Sprite() 
    result.expand(pStart) 
    marks = [] 
    while (len(q) > 0): 
    p = q.pop(0) 
    result.expand(p) 
    neighbouring = loadEightNeighbouringPixels(p, img) 
    for n in neighbouring: 
     if img.getpixel(n)[3] > 0 and not n in marks: 
     marks.append(n) 
     q.append(n) 
    return result 

def loadFourNeighbouringPixels (point, img): 
    result = None 
    result = [] 

    newPoint = (point[0], point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0], point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    return result 

def loadEightNeighbouringPixels (point, img): 
    result = None 
    result = [] 

    newPoint = (point[0], point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0], point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    return result 

im = Image.open("test2.png") 
print(im.format, im.size, im.mode) 
#PNG (640, 252) RGBA 
#im.show() 
print("width = " + str(im.width)) 
print("height = " + str(im.height)) 



sprites = [] 
for y in range(im.height): 
    for x in range(im.width): 
    pixel = im.getpixel((x, y)) 
    haycolor = True if pixel[3] > 0 else False 
    if (haycolor): 
     pos = (x, y) 
     #print("(" + str(x) + ", " + str(y) + ") -> " + str(pixel)) 
     pixelP = pixel 
     sprite = loadSprite(pos, sprites) 
     if (sprite != None): 
     x = sprite.end_x 
     else: 
     sprite = exploreBoundedBox(pos, im) 
     sprites.append(sprite) 

print("sprites") 
print(str(sprites)) 
idx = 1 
for sprite in sprites: 
    print("sprite " + str(idx) + ". -> " + str(sprite)) 
    imSprite = im.crop((sprite.start_x, sprite.start_y, sprite.end_x + 1, sprite.end_y + 1)) 
    #imSprite.show() 
    imSprite.save("sprite" + str(idx) + ".png") 
    idx += 1 

爲了避免留下的是精靈的一小部分,我們必須添加以下改進:

MINIMUM_SPRITE = 8 

def firstNonSprites (sprites): 
    result = None 
    for sprite in sprites: 
    if (sprite.end_x - sprite.start_x + 1) < MINIMUM_SPRITE or (sprite.end_y - sprite.start_y + 1) < MINIMUM_SPRITE: 
     result = sprite 
     break 
    return result 

def mergeSprites (sprite1, sprite2): 
    result = None 
    if (sprite1 != None and sprite2 != None): 
    result = Sprite() 
    result.start_x = min(sprite1.start_x, sprite2.start_x) 
    result.start_y = min(sprite1.start_y, sprite2.start_y) 
    result.end_x = max(sprite1.end_x, sprite2.end_x) 
    result.end_y = max(sprite1.end_y, sprite2.end_y) 
    return result 

def findNextSprite (pivot, sprites): 
    result = None 
    distance = 99999999 
    for sprite in sprites: 
    if sprite != pivot: 
     itemDistance = distanceSprites(pivot, sprite) 
     if (itemDistance < distance): 
     distance = itemDistance 
     result = sprite 
    return result 

#Pitagoras 
def distancePoints (point1, point2): 
    result = 99999999 
    if (point1 != None and point2 != None): 
    a = abs(point2[0] - point1[0]) 
    b = abs(point2[1] - point1[1]) 
    result = math.sqrt(math.pow(a, 2) + math.pow(b, 2)) 
    return result 

def distancePointSprite (point, sprite): 
    result = 99999999 
    if (point != None and sprite != None): 
    distance = distancePoints(point, (sprite.start_x, sprite.start_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.end_x, sprite.start_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.start_x, sprite.end_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.end_x, sprite.end_y)) 
    if (distance < result): 
     result = distance 
    return result 


def distanceSprites (sprite1, sprite2): 
    result = 99999999 
    if (sprite1 != None and sprite2 != None): 
    distance = distancePointSprite((sprite1.start_x, sprite1.start_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.end_x, sprite1.start_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.start_x, sprite1.end_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.end_x, sprite1.end_y), sprite2) 
    if (distance < result): 
     result = distance 
    return result 

def fixMergeSprites (sprites): 
    result = [] 
    pivotNonSprite = firstNonSprites(sprites) 
    while (pivotNonSprite != None): 
    nextSprite = findNextSprite(pivotNonSprite, sprites) 
    if nextSprite == None: 
     break 
    mergeSprite = mergeSprites(pivotNonSprite, nextSprite) 
    sprites.remove(nextSprite) 
    sprites.remove(pivotNonSprite) 
    sprites.append(mergeSprite) 
    pivotNonSprite = firstNonSprites(sprites) 
    result = sprites 
    return result 

#BEFORE CROP 
sprites = fixMergeSprites(sprites) 

全碼:

#!/usr/bin/env python 
#coding:utf-8 

from __future__ import print_function 
from PIL import Image 
import math 

#https://stackoverflow.com/questions/13584586/sprite-sheet-detect-individual-sprite-bounds-automatically?rq=1 
''' 
Process each* scan line in turn 
    For each scanline, walk from left to right, until you find a non-transparent pixel P. 
    If the location of P is already inside a known bounded box 
     Continue to the right of the bounded box 
    Else 
     BBox = ExploreBoundedBox(P) 
     Add BBox to the collection of known bounded boxes 

Function ExploreBoundedBox(pStart) 
    Q = new Queue(pStart) 
    B = new BoundingBox(pStart) 

    While Q is not empty 
    Dequeue the front element as P 
    Expand B to include P 

    For each of the four neighbouring pixels N 
     If N is not transparent and N is not marked 
     Mark N 
     Enqueue N at the back of Q 

    return B 
''' 

class Sprite: 
    def __init__(self): 
    self.start_x = -1 
    self.start_y = -1 
    self.end_x = -1 
    self.end_y = -1 

    def expand (self, point): 
    if (self.start_x < 0 and self.start_y < 0 and self.end_x < 0 and self.end_y < 0): 
     self.start_x = point[0] 
     self.start_y = point[1] 
     self.end_x = point[0] 
     self.end_y = point[1] 
    else: 
     if (point[0] < self.start_x): 
     self.start_x = point[0] 
     if (point[0] > self.end_x): 
     self.end_x = point[0] 
     if (point[1] < self.start_y): 
     self.start_y = point[1] 
     if (point[1] > self.end_y): 
     self.end_y = point[1] 

    def belongs (self, point): 
    result = False 
    result = True 
    result = result and point[0] >= self.start_x and point[0] <= self.end_x 
    result = result and point[1] >= self.start_y and point[1] <= self.end_y 
    return result 

    def __str__(self): 
    result = "" 
    result = result + "(" 
    result = result + str(self.start_x) 
    result = result + ", " 
    result = result + str(self.start_y) 
    result = result + ", " 
    result = result + str(self.end_x) 
    result = result + ", " 
    result = result + str(self.end_y) 
    result = result + ")" 
    return result 

def loadSprite (pos, sprites): 
    result = None 
    for sprite in sprites: 
    if sprite.belongs(pos): 
     result = sprite 
     break 
    return result 


''' 
Function ExploreBoundedBox(pStart) 
    Q = new Queue(pStart) 
    B = new BoundingBox(pStart) 

    While Q is not empty 
    Dequeue the front element as P 
    Expand B to include P 

    For each of the four neighbouring pixels N 
     If N is not transparent and N is not marked 
     Mark N 
     Enqueue N at the back of Q 

    return B 
''' 
def exploreBoundedBox (pStart, img): 
    result = None 
    q = [] 
    q.append(pStart) 
    result = Sprite() 
    result.expand(pStart) 
    marks = [] 
    while (len(q) > 0): 
    p = q.pop(0) 
    result.expand(p) 
    neighbouring = loadEightNeighbouringPixels(p, img) 
    for n in neighbouring: 
     if img.getpixel(n)[3] > 0 and not n in marks: 
     marks.append(n) 
     q.append(n) 
    return result 

def loadFourNeighbouringPixels (point, img): 
    result = None 
    result = [] 

    newPoint = (point[0], point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0], point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    return result 

def loadEightNeighbouringPixels (point, img): 
    result = None 
    result = [] 

    newPoint = (point[0], point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1]) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0], point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1] - 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] - 1, point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    newPoint = (point[0] + 1, point[1] + 1) 
    if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): 
    result.append(newPoint) 

    return result 

MINIMUM_SPRITE = 8 

def firstNonSprites (sprites): 
    result = None 
    for sprite in sprites: 
    if (sprite.end_x - sprite.start_x + 1) < MINIMUM_SPRITE or (sprite.end_y - sprite.start_y + 1) < MINIMUM_SPRITE: 
     result = sprite 
     break 
    return result 

def mergeSprites (sprite1, sprite2): 
    result = None 
    if (sprite1 != None and sprite2 != None): 
    result = Sprite() 
    result.start_x = min(sprite1.start_x, sprite2.start_x) 
    result.start_y = min(sprite1.start_y, sprite2.start_y) 
    result.end_x = max(sprite1.end_x, sprite2.end_x) 
    result.end_y = max(sprite1.end_y, sprite2.end_y) 
    return result 

def findNextSprite (pivot, sprites): 
    result = None 
    distance = 99999999 
    for sprite in sprites: 
    if sprite != pivot: 
     itemDistance = distanceSprites(pivot, sprite) 
     if (itemDistance < distance): 
     distance = itemDistance 
     result = sprite 
    return result 

#Pitagoras 
def distancePoints (point1, point2): 
    result = 99999999 
    if (point1 != None and point2 != None): 
    a = abs(point2[0] - point1[0]) 
    b = abs(point2[1] - point1[1]) 
    result = math.sqrt(math.pow(a, 2) + math.pow(b, 2)) 
    return result 

def distancePointSprite (point, sprite): 
    result = 99999999 
    if (point != None and sprite != None): 
    distance = distancePoints(point, (sprite.start_x, sprite.start_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.end_x, sprite.start_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.start_x, sprite.end_y)) 
    if (distance < result): 
     result = distance 
    distance = distancePoints(point, (sprite.end_x, sprite.end_y)) 
    if (distance < result): 
     result = distance 
    return result 


def distanceSprites (sprite1, sprite2): 
    result = 99999999 
    if (sprite1 != None and sprite2 != None): 
    distance = distancePointSprite((sprite1.start_x, sprite1.start_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.end_x, sprite1.start_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.start_x, sprite1.end_y), sprite2) 
    if (distance < result): 
     result = distance 
    distance = distancePointSprite((sprite1.end_x, sprite1.end_y), sprite2) 
    if (distance < result): 
     result = distance 
    return result 

def fixMergeSprites (sprites): 
    result = [] 
    pivotNonSprite = firstNonSprites(sprites) 
    while (pivotNonSprite != None): 
    nextSprite = findNextSprite(pivotNonSprite, sprites) 
    if nextSprite == None: 
     break 
    mergeSprite = mergeSprites(pivotNonSprite, nextSprite) 
    sprites.remove(nextSprite) 
    sprites.remove(pivotNonSprite) 
    sprites.append(mergeSprite) 
    pivotNonSprite = firstNonSprites(sprites) 
    result = sprites 
    return result 

im = Image.open("test.png") 
print(im.format, im.size, im.mode) 
#PNG (640, 252) RGBA 
#im.show() 
print("width = " + str(im.width)) 
print("height = " + str(im.height)) 



sprites = [] 
for y in range(im.height): 
    for x in range(im.width): 
    pixel = im.getpixel((x, y)) 
    haycolor = True if pixel[3] > 0 else False 
    if (haycolor): 
     pos = (x, y) 
     #print("(" + str(x) + ", " + str(y) + ") -> " + str(pixel)) 
     pixelP = pixel 
     sprite = loadSprite(pos, sprites) 
     if (sprite != None): 
     x = sprite.end_x 
     else: 
     sprite = exploreBoundedBox(pos, im) 
     sprites.append(sprite) 

sprites = fixMergeSprites(sprites) 

print("sprites") 
print(str(sprites)) 
idx = 1 
for sprite in sprites: 
    print("sprite " + str(idx) + ". -> " + str(sprite)) 
    imSprite = im.crop((sprite.start_x, sprite.start_y, sprite.end_x + 1, sprite.end_y + 1)) 
    #imSprite.show() 
    imSprite.save("sprite" + str(idx) + ".png") 
    idx += 1