給定一個精靈表是這樣的:精靈表單檢測個別的Sprite界自動
我想寫入的算法,可以循環通過所述像素數據,並確定各分立子畫面的邊界矩形。如果我們假設對於每個像素X,Y我可以拉真(像素是不是完全透明)或假(像素是完全透明),我將如何去自動生成每個精靈的邊界矩形?
生成的數據應該是具有{x,y,寬度,高度}的矩形對象的數組。
這裏是相同的圖像,但註明在淡藍色的前四個精靈的界限:
誰能給一個一步一步如何如上所述檢測這些界限?
給定一個精靈表是這樣的:精靈表單檢測個別的Sprite界自動
我想寫入的算法,可以循環通過所述像素數據,並確定各分立子畫面的邊界矩形。如果我們假設對於每個像素X,Y我可以拉真(像素是不是完全透明)或假(像素是完全透明),我將如何去自動生成每個精靈的邊界矩形?
生成的數據應該是具有{x,y,寬度,高度}的矩形對象的數組。
這裏是相同的圖像,但註明在淡藍色的前四個精靈的界限:
誰能給一個一步一步如何如上所述檢測這些界限?
這個怎麼樣?唯一的缺點是你需要一個可寫的圖像版本來標記訪問的像素,否則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.如果您沒有可寫的圖像版本,則可以使用Set來跟蹤標記的像素。 2.當找到'ExploreBoundedBox'中的相鄰像素時,獲得_eight_相鄰像素(即對角線)可能會更好。看第一排的松樹;其最右邊的像素僅與主體對角連接。 – Kevin
謝謝@Astrotrain和凱文。我要實現這個並讓你知道結果! –
@凱文:你說得對,好點。但是我建議使用Hashtable而不是Set,因爲它比大集合的性能更好(並且精靈中的像素數量非常大)。在圖像內部繪製仍然是最快的,所以如果這是一個選項... – Astrotrain
連接實現對Python和枕頭:
網址:https://gist.github.com/tuaplicacionpropia/f5bd6b0f69a11141767387eb789f5093
#!/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
難道精靈總是完全連接? –
並非總是如此,但重要的一點是它們之間會存在一條直線的透明度。 –