Dev: [ALAS] Extract ButtonGrid assets

This commit is contained in:
LmeSzinc 2024-05-27 02:54:51 +08:00
parent 783658be0b
commit d615886993
2 changed files with 98 additions and 13 deletions

View File

@ -3,11 +3,13 @@ import re
import typing as t import typing as t
from dataclasses import dataclass from dataclasses import dataclass
import cv2
import numpy as np import numpy as np
from tqdm import tqdm from tqdm import tqdm
from module.base.code_generator import CodeGenerator from module.base.code_generator import CodeGenerator
from module.base.utils import SelectedGrids, area_limit, area_pad, get_bbox, get_color, image_size, load_image from module.base.utils import (
SelectedGrids, area_center, area_limit, area_pad, corner2area, get_bbox, get_color, image_size, load_image)
from module.config.config_manual import ManualConfig as AzurLaneConfig from module.config.config_manual import ManualConfig as AzurLaneConfig
from module.config.server import VALID_LANG from module.config.server import VALID_LANG
from module.config.utils import deep_get, deep_iter, deep_set, iter_folder from module.config.utils import deep_get, deep_iter, deep_set, iter_folder
@ -17,6 +19,44 @@ SHARE_SERVER = 'share'
ASSET_SERVER = [SHARE_SERVER] + VALID_LANG ASSET_SERVER = [SHARE_SERVER] + VALID_LANG
def parse_grid(image):
"""
Args:
image:
Returns:
dict: Key: Grid position (x, y)
Value: Area on image
"""
image = cv2.inRange(image, (127, 127, 127), (255, 255, 255))
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
dic_rect = {}
for corners in contours:
area = corner2area(corners.reshape(4, 2)) + (0, 0, 1, 1)
center = area_center(area)
dic_rect[center] = area
# for k, v in dic_rect.items():
# print(k, v)
dic_grid = {}
prev_y = -100
grid_y = -1
stack_center = []
for center in sorted(dic_rect.keys(), key=lambda x: x[1]):
if center[1] > prev_y + 3:
for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])):
dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int))
grid_y += 1
stack_center = []
stack_center.append(center)
prev_y = center[1]
for x, c in enumerate(sorted(stack_center, key=lambda x: x[0])):
dic_grid[(x, grid_y)] = tuple(dic_rect[c].astype(int))
# for k, v in dic_grid.items():
# print(k, v)
return dic_grid
class AssetsImage: class AssetsImage:
REGEX_ASSETS = re.compile( REGEX_ASSETS = re.compile(
f'^{AzurLaneConfig.ASSETS_FOLDER}/' f'^{AzurLaneConfig.ASSETS_FOLDER}/'
@ -24,7 +64,7 @@ class AssetsImage:
f'(?P<module>[a-zA-Z0-9_/]+?)/' f'(?P<module>[a-zA-Z0-9_/]+?)/'
f'(?P<assets>\w+)' f'(?P<assets>\w+)'
f'(?P<frame>\.\d+)?' f'(?P<frame>\.\d+)?'
f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON)?' f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON|\.GRID)?'
f'\.png$' f'\.png$'
) )
@ -46,6 +86,7 @@ class AssetsImage:
self.assets = '' self.assets = ''
self.frame = 1 self.frame = 1
self.attr = '' self.attr = ''
self.posi = None
if res: if res:
self.valid = True self.valid = True
@ -66,6 +107,7 @@ class AssetsImage:
self.bbox: t.Tuple = () self.bbox: t.Tuple = ()
self.mean: t.Tuple = () self.mean: t.Tuple = ()
self.grids = {}
def parse(self): def parse(self):
image = load_image(self.file) image = load_image(self.file)
@ -79,6 +121,10 @@ class AssetsImage:
mean = tuple(np.rint(mean).astype(int)) mean = tuple(np.rint(mean).astype(int))
self.bbox = bbox self.bbox = bbox
self.mean = mean self.mean = mean
if self.attr == 'GRID':
self.grids = parse_grid(image)
return bbox, mean return bbox, mean
def __str__(self): def __str__(self):
@ -87,6 +133,26 @@ class AssetsImage:
else: else:
return f'AssetsImage(file={self.file}, valid={self.valid})' return f'AssetsImage(file={self.file}, valid={self.valid})'
@property
def is_GRID(self):
return self.attr == 'GRID'
@property
def is_base(self):
return self.attr == ''
def iter_grids(self):
frame = 0
for posi, rect in self.grids.items():
frame += 1
image = AssetsImage(self.file)
image.attr = ''
image.bbox = rect
image.mean = self.mean
image.frame = frame
image.posi = posi
yield image
def iter_images(): def iter_images():
for server in ASSET_SERVER: for server in ASSET_SERVER:
@ -97,6 +163,12 @@ def iter_images():
yield AssetsImage(file) yield AssetsImage(file)
def iter_grids(images):
for image in images:
for grid in image.iter_grids():
yield grid
@dataclass @dataclass
class DataAssets: class DataAssets:
module: str module: str
@ -104,6 +176,7 @@ class DataAssets:
server: str server: str
frame: int frame: int
file: str = '' file: str = ''
posi = None
area: t.Tuple[int, int, int, int] = () area: t.Tuple[int, int, int, int] = ()
search: t.Tuple[int, int, int, int] = () search: t.Tuple[int, int, int, int] = ()
color: t.Tuple[int, int, int] = () color: t.Tuple[int, int, int] = ()
@ -126,7 +199,6 @@ class DataAssets:
Product DataAssets from AssetsImage with attr="" Product DataAssets from AssetsImage with attr=""
""" """
data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file) data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file)
data.load_image(image)
return data return data
def load_image(self, image: AssetsImage): def load_image(self, image: AssetsImage):
@ -135,6 +207,7 @@ class DataAssets:
self.area = image.bbox self.area = image.bbox
self.color = image.mean self.color = image.mean
self.button = image.bbox self.button = image.bbox
self.posi = image.posi
elif image.attr == 'AREA': elif image.attr == 'AREA':
self.area = image.bbox self.area = image.bbox
self.has_raw_area = True self.has_raw_area = True
@ -147,6 +220,8 @@ class DataAssets:
elif image.attr == 'BUTTON': elif image.attr == 'BUTTON':
self.button = image.bbox self.button = image.bbox
self.has_raw_button = True self.has_raw_button = True
elif image.attr == 'GRID':
pass
else: else:
logger.warning(f'Trying to load an image with unknown attribute: {image}') logger.warning(f'Trying to load an image with unknown attribute: {image}')
@ -161,17 +236,22 @@ def iter_assets():
for image in tqdm(images): for image in tqdm(images):
image.parse() image.parse()
images += list(iter_grids(images))
# Validate images # Validate images
images = SelectedGrids(images).select(valid=True) images = SelectedGrids(images).select(valid=True)
images.create_index('module', 'assets', 'server', 'frame', 'attr') images.create_index('module', 'assets', 'server', 'frame', 'attr')
for image in images.filter(lambda x: bool(x.attr)): for image in images.filter(lambda x: bool(x.attr)):
image: AssetsImage = image image: AssetsImage = image
if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''): if image.is_GRID:
logger.warning(f'Attribute assets has no parent assets: {image.file}') pass
image.valid = False else:
if not images.indexed_select(image.module, image.assets, image.server, 1, ''): if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''):
logger.warning(f'Attribute assets has no first frame: {image.file}') logger.warning(f'Attribute assets has no parent assets: {image.file}')
image.valid = False image.valid = False
if not images.indexed_select(image.module, image.assets, image.server, 1, ''):
logger.warning(f'Attribute assets has no first frame: {image.file}')
image.valid = False
if image.attr == 'SEARCH' and image.frame > 1: if image.attr == 'SEARCH' and image.frame > 1:
logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}') logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}')
image.valid = False image.valid = False
@ -180,18 +260,18 @@ def iter_assets():
# Convert to DataAssets # Convert to DataAssets
data = {} data = {}
for image in images: for image in images:
if image.attr == '': if image.is_base:
row = DataAssets.product(image) row = DataAssets.product(image)
row.load_image(image) row.load_image(image)
deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row) deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row)
# Load attribute images # Load attribute images
for image in images: for image in images:
if image.attr != '': if not image.is_base:
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame]) row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
row.load_image(image) row.load_image(image)
# Set `search` # Set `search`
for path, frames in deep_iter(data, depth=3): for path, frames in deep_iter(data, depth=3):
print(path, frames) # print(path, frames)
for frame in frames.values(): for frame in frames.values():
# Generate `search` from `area` # Generate `search` from `area`
if not frame.has_raw_search: if not frame.has_raw_search:
@ -253,6 +333,8 @@ def generate_code():
gen.ObjectAttr(key='search', value=frame.search) gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color) gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button) gen.ObjectAttr(key='button', value=frame.button)
if frame.posi is not None:
gen.ObjectAttr(key='posi', value=frame.posi)
elif len(frames) == 1: elif len(frames) == 1:
frame = frames[0] frame = frames[0]
with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')): with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')):
@ -261,6 +343,8 @@ def generate_code():
gen.ObjectAttr(key='search', value=frame.search) gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color) gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button) gen.ObjectAttr(key='button', value=frame.button)
if frame.posi is not None:
gen.ObjectAttr(key='posi', value=frame.posi)
else: else:
gen.ObjectAttr(key=server, value=None) gen.ObjectAttr(key=server, value=None)
gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py')) gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py'))

View File

@ -6,7 +6,7 @@ from module.exception import ScriptError
class Button(Resource): class Button(Resource):
def __init__(self, file, area, search, color, button): def __init__(self, file, area, search, color, button, posi=None):
""" """
Args: Args:
file: Filepath to an assets file: Filepath to an assets
@ -20,6 +20,7 @@ class Button(Resource):
self.search: t.Tuple[int, int, int, int] = search self.search: t.Tuple[int, int, int, int] = search
self.color: t.Tuple[int, int, int] = color self.color: t.Tuple[int, int, int] = color
self._button: t.Tuple[int, int, int, int] = button self._button: t.Tuple[int, int, int, int] = button
self.posi: t.Optional[t.Tuple[int, int]] = posi
self.resource_add(self.file) self.resource_add(self.file)
self._button_offset: t.Tuple[int, int] = (0, 0) self._button_offset: t.Tuple[int, int] = (0, 0)