"""Texture Atlas for SpriteListThe long term goal is to rely on pyglet's texture atlas, butit's still unclear what features we need supported in arcadeso need to prototype something to get started.We're still building on pyglet's allocator.Pyglet atlases are located here:https://github.com/einarf/pyglet/blob/master/pyglet/image/atlas.pyAllocation:Pyglet's allocator is a simple row based allocator only keepingtrack of horizontal strips and how far in the x direction theeach strip is filled. We can't really "deallocate" unless it'sa region at the end of a strip and even doing that is awkward.When an image is removed from the atlas we simply just lose thatregion until we rebuild the atlas. It can be a good idea to countthe number of lost regions to use as an indicator later. When anatlas is full we can first rebuild it if there are lost regionsinstead of increasing the size."""from__future__importannotationsimportabcimportloggingfromtypingimport(Dict,Optional,TYPE_CHECKING,)importarcadeifTYPE_CHECKING:fromarcadeimportArcadeContext,Texturefromarcade.textureimportImageData# The amount of pixels we increase the atlas when scanning for a reasonable size.# It must divide. Must be a power of two number like 64, 256, 512 etxRESIZE_STEP=128UV_TEXTURE_WIDTH=4096LOG=logging.getLogger(__name__)classImageDataRefCounter:""" Helper class to keep track of how many times an image is used by a texture in the atlas to determine when it's safe to remove it. Multiple Texture instances can and will use the same ImageData instance. """def__init__(self)->None:self._data:Dict[str,int]={}self._num_decref=0definc_ref(self,image_data:"ImageData")->None:"""Increment the reference count for an image."""self._data[image_data.hash]=self._data.get(image_data.hash,0)+1defdec_ref(self,image_data:"ImageData")->int:""" Decrement the reference count for an image returning the new value. """ifimage_data.hashnotinself._data:raiseRuntimeError(f"Image {image_data.hash} not in ref counter")val=self._data[image_data.hash]-1self._data[image_data.hash]=valifval<0:raiseRuntimeError(f"Image {image_data.hash} ref count went below zero")ifval==0:delself._data[image_data.hash]self._num_decref+=1returnvaldefget_ref_count(self,image_data:"ImageData")->int:""" Get the reference count for an image. Args: image_data (ImageData): The image to get the reference count for """returnself._data.get(image_data.hash,0)defcount_all_refs(self)->int:"""Helper function to count the total number of references."""returnsum(self._data.values())defget_total_decref(self,reset=True)->int:""" Get the total number of decrefs. Args: reset (bool): Reset the counter after getting the value """num_decref=self._num_decrefifreset:self._num_decref=0returnnum_decrefdefclear(self)->None:"""Clear the reference counter."""self._data.clear()self._num_decref=0def__len__(self)->int:returnlen(self._data)def__repr__(self)->str:returnf"<ImageDataRefCounter ref_count={self.count_all_refs()} data={self._data}>"
[docs]classTextureAtlasBase(abc.ABC):"""Generic base for texture atlases."""def__init__(self,ctx:Optional["ArcadeContext"]):self._ctx=ctxorarcade.get_window().ctx@propertydefctx(self)->"ArcadeContext":returnself._ctx# NOTE: AtlasRegion only makes sense for 2D atlas. Figure it out.# @abc.abstractmethod# def add(self, texture: "Texture") -> Tuple[int, AtlasRegion]:# """Add a texture to the atlas."""# raise NotImplementedError
[docs]@abc.abstractmethoddefremove(self,texture:"Texture")->None:"""Remove a texture from the atlas."""raiseNotImplementedError