218 lines
9.0 KiB
GDScript
218 lines
9.0 KiB
GDScript
@tool
|
|
extends "_.gd"
|
|
|
|
const _RectPacker = preload("../rect_packer.gd")
|
|
|
|
class SpriteProps:
|
|
extends RefCounted
|
|
var images_props: Array[ImageProps]
|
|
var atlas_region_props: AtlasRegionProps
|
|
|
|
var offset: Vector2i
|
|
|
|
func create_sprite(atlas_region_position: Vector2i) -> _Common.SpriteInfo:
|
|
var sprite = _Common.SpriteInfo.new()
|
|
sprite.region = Rect2i(atlas_region_position, atlas_region_props.size)
|
|
sprite.offset = offset
|
|
return sprite
|
|
|
|
class AtlasRegionProps:
|
|
extends RefCounted
|
|
var sprites_props: Array[SpriteProps]
|
|
var images_props: Array[ImageProps]
|
|
|
|
var size: Vector2i
|
|
|
|
class ImageProps:
|
|
extends RefCounted
|
|
var sprite_props: SpriteProps
|
|
var atlas_region_props: AtlasRegionProps
|
|
|
|
var image: Image
|
|
var used_rect: Rect2i
|
|
var used_fragment: Image
|
|
var used_fragment_data: PackedByteArray
|
|
var used_fragment_data_hash: int
|
|
|
|
func _init(image: Image) -> void:
|
|
self.image = image
|
|
used_rect = image.get_used_rect()
|
|
if used_rect.has_area():
|
|
used_fragment = image.get_region(used_rect)
|
|
used_fragment_data = used_fragment.get_data()
|
|
used_fragment_data_hash = hash(used_fragment_data)
|
|
|
|
class SpriteSheetBuildingContext:
|
|
extends RefCounted
|
|
var images_props: Array[ImageProps]
|
|
var sprites_props: Array[SpriteProps]
|
|
var atlas_regions_props: Array[AtlasRegionProps]
|
|
var _similar_images_props_by_used_fragment_data_hash: Dictionary
|
|
var _collapsed_image_props: ImageProps
|
|
|
|
func _init(images: Array[Image]) -> void:
|
|
var images_count: int = images.size()
|
|
images_props.resize(images_count)
|
|
for image_index in images_count:
|
|
images_props[image_index] = _process_image_props(ImageProps.new(images[image_index]))
|
|
|
|
func _process_image_props(image_props: ImageProps) -> ImageProps:
|
|
if not image_props.used_rect.has_area():
|
|
if _collapsed_image_props == null:
|
|
_collapsed_image_props = image_props
|
|
return _collapsed_image_props
|
|
|
|
var similar_images_props: Array[ImageProps]
|
|
if not _similar_images_props_by_used_fragment_data_hash.has(image_props.used_fragment_data_hash):
|
|
_similar_images_props_by_used_fragment_data_hash[image_props.used_fragment_data_hash] = similar_images_props
|
|
else:
|
|
similar_images_props = _similar_images_props_by_used_fragment_data_hash[image_props.used_fragment_data_hash]
|
|
for similar_image_props in similar_images_props:
|
|
if image_props.image == similar_image_props.image:
|
|
# The same image found.
|
|
return similar_image_props
|
|
elif image_props.used_rect.size == similar_image_props.used_rect.size:
|
|
if image_props.used_fragment_data == similar_image_props.used_fragment_data:
|
|
if image_props.used_rect.position == similar_image_props.used_rect.position:
|
|
# An image with equal content found.
|
|
return similar_image_props
|
|
else:
|
|
# An image with equal, but offsetted content found.
|
|
# It will have the same region, but new sprite.
|
|
image_props.atlas_region_props = similar_image_props.atlas_region_props
|
|
image_props.sprite_props = SpriteProps.new()
|
|
image_props.sprite_props.offset = image_props.used_rect.position
|
|
image_props.sprite_props.images_props.push_back(image_props)
|
|
image_props.sprite_props.atlas_region_props = similar_image_props.atlas_region_props
|
|
sprites_props.push_back(image_props.sprite_props)
|
|
return image_props
|
|
# A new unique image found.
|
|
# It will have new region and sprite.
|
|
image_props.atlas_region_props = AtlasRegionProps.new()
|
|
image_props.atlas_region_props.size = image_props.used_rect.size
|
|
image_props.sprite_props = SpriteProps.new()
|
|
image_props.sprite_props.offset = image_props.used_rect.position
|
|
image_props.sprite_props.images_props.push_back(image_props)
|
|
image_props.sprite_props.atlas_region_props = image_props.atlas_region_props
|
|
image_props.atlas_region_props.sprites_props.push_back(image_props.sprite_props)
|
|
image_props.atlas_region_props.images_props.push_back(image_props)
|
|
sprites_props.push_back(image_props.sprite_props)
|
|
atlas_regions_props.push_back(image_props.atlas_region_props)
|
|
similar_images_props.push_back(image_props)
|
|
return image_props
|
|
|
|
func build_sprite_sheet(images: Array[Image]) -> SpriteSheetBuildingResult:
|
|
var result: SpriteSheetBuildingResult = SpriteSheetBuildingResult.new()
|
|
var images_count: int = images.size()
|
|
|
|
var sprite_sheet: _Common.SpriteSheetInfo = _Common.SpriteSheetInfo.new()
|
|
|
|
if images_count == 0:
|
|
var atlas_image = Image.new()
|
|
atlas_image.set_data(1, 1, false, Image.FORMAT_RGBA8, PackedByteArray([0, 0, 0, 0]))
|
|
result.success(sprite_sheet, atlas_image)
|
|
return result
|
|
|
|
sprite_sheet.source_image_size = images.front().get_size()
|
|
if not images.all(func(i: Image) -> bool:
|
|
return i.get_size() == sprite_sheet.source_image_size):
|
|
result.fail(ERR_INVALID_DATA, "Input images have different sizes")
|
|
return result
|
|
|
|
sprite_sheet.sprites.resize(images_count)
|
|
|
|
var context: SpriteSheetBuildingContext = SpriteSheetBuildingContext.new(images)
|
|
var atlas_regions_count: int = context.atlas_regions_props.size()
|
|
if atlas_regions_count == 0:
|
|
# All sprites are collapsed
|
|
var collapsed_sprite: _Common.SpriteInfo = _Common.SpriteInfo.new()
|
|
for image_index in images_count:
|
|
sprite_sheet.sprites[image_index] = collapsed_sprite
|
|
var atlas_image = Image.new()
|
|
atlas_image.set_data(1, 1, false, Image.FORMAT_RGBA8, PackedByteArray([0, 0, 0, 0]))
|
|
result.success(sprite_sheet, atlas_image)
|
|
return result
|
|
|
|
var atlas_regions_sizes: Array[Vector2i]
|
|
atlas_regions_sizes.resize(atlas_regions_count)
|
|
for atlas_region_index in atlas_regions_count:
|
|
atlas_regions_sizes[atlas_region_index] = \
|
|
context.atlas_regions_props[atlas_region_index].size
|
|
|
|
match _edges_artifacts_avoidance_method:
|
|
_Common.EdgesArtifactsAvoidanceMethod.TRANSPARENT_SPACING, \
|
|
_Common.EdgesArtifactsAvoidanceMethod.SOLID_COLOR_SURROUNDING:
|
|
for atlas_region_index in atlas_regions_count:
|
|
atlas_regions_sizes[atlas_region_index] += Vector2i.ONE
|
|
_Common.EdgesArtifactsAvoidanceMethod.BORDERS_EXTRUSION, \
|
|
_Common.EdgesArtifactsAvoidanceMethod.TRANSPARENT_EXPANSION:
|
|
for atlas_region_index in atlas_regions_count:
|
|
atlas_regions_sizes[atlas_region_index] += Vector2i.ONE * 2
|
|
|
|
var packing_result: _RectPacker.RectPackingResult = _RectPacker.pack(atlas_regions_sizes)
|
|
if packing_result.error:
|
|
result.fail(ERR_BUG, "Rect packing failed", packing_result)
|
|
return result
|
|
|
|
match _edges_artifacts_avoidance_method:
|
|
_Common.EdgesArtifactsAvoidanceMethod.TRANSPARENT_SPACING:
|
|
packing_result.bounds -= Vector2i.ONE
|
|
_Common.EdgesArtifactsAvoidanceMethod.SOLID_COLOR_SURROUNDING:
|
|
packing_result.bounds += Vector2i.ONE
|
|
for atlas_region_index in atlas_regions_count:
|
|
packing_result.rects_positions[atlas_region_index] += Vector2i.ONE
|
|
_Common.EdgesArtifactsAvoidanceMethod.BORDERS_EXTRUSION:
|
|
for atlas_region_index in atlas_regions_count:
|
|
packing_result.rects_positions[atlas_region_index] += Vector2i.ONE
|
|
|
|
var atlas_image: Image = Image.create(
|
|
packing_result.bounds.x, packing_result.bounds.y, false, Image.FORMAT_RGBA8)
|
|
|
|
if _edges_artifacts_avoidance_method == _Common.EdgesArtifactsAvoidanceMethod.SOLID_COLOR_SURROUNDING:
|
|
atlas_image.fill(_sprites_surrounding_color)
|
|
|
|
var extrude_sprites_borders: bool = _edges_artifacts_avoidance_method == \
|
|
_Common.EdgesArtifactsAvoidanceMethod.BORDERS_EXTRUSION
|
|
var expand_sprites: bool = _edges_artifacts_avoidance_method == \
|
|
_Common.EdgesArtifactsAvoidanceMethod.TRANSPARENT_EXPANSION
|
|
|
|
var atlas_regions_positions_by_atlas_regions_props: Dictionary
|
|
for atlas_region_index in atlas_regions_count:
|
|
var atlas_region_props: AtlasRegionProps = context.atlas_regions_props[atlas_region_index]
|
|
packing_result.rects_positions[atlas_region_index] += \
|
|
Vector2i.ONE if expand_sprites else Vector2i.ZERO
|
|
var image_props: ImageProps = atlas_region_props.images_props.front()
|
|
atlas_image.blit_rect(image_props.used_fragment,
|
|
Rect2i(Vector2i.ZERO, atlas_region_props.size),
|
|
packing_result.rects_positions[atlas_region_index])
|
|
if extrude_sprites_borders:
|
|
_extrude_borders(atlas_image, Rect2i(
|
|
packing_result.rects_positions[atlas_region_index],
|
|
atlas_region_props.size))
|
|
atlas_regions_positions_by_atlas_regions_props[atlas_region_props] = \
|
|
packing_result.rects_positions[atlas_region_index]
|
|
|
|
var sprites_by_sprites_props: Dictionary
|
|
for sprite_props in context.sprites_props:
|
|
var sprite: _Common.SpriteInfo = sprite_props.create_sprite(
|
|
atlas_regions_positions_by_atlas_regions_props[sprite_props.atlas_region_props])
|
|
if expand_sprites:
|
|
sprite.region = sprite.region.grow(1)
|
|
sprite.offset -= Vector2i.ONE
|
|
sprites_by_sprites_props[sprite_props] = sprite
|
|
|
|
var collapsed_sprite: _Common.SpriteInfo
|
|
for image_index in images_count:
|
|
var image_props: ImageProps = context.images_props[image_index]
|
|
var sprite: _Common.SpriteInfo = sprites_by_sprites_props.get(image_props.sprite_props, null)
|
|
if sprite == null:
|
|
if collapsed_sprite == null:
|
|
collapsed_sprite = _Common.SpriteInfo.new()
|
|
sprite = collapsed_sprite
|
|
sprite_sheet.sprites[image_index] = sprite
|
|
|
|
if expand_sprites:
|
|
sprite_sheet.source_image_size += Vector2i.ONE * 2
|
|
result.success(sprite_sheet, atlas_image)
|
|
return result
|