コード例 #1
0
def main(args):
    try:
        src_dir = Path(os.environ['MESON_SOURCE_ROOT'])
        build_dir = Path(os.environ['MESON_BUILD_ROOT'])
    except KeyError:
        print("This script should not be ran directly", file=sys.stderr)
        exit(1)

    with ThreadPoolExecutor() as ex:
        futures = []

        for target in args[1:]:
            futures.append(ex.submit(lambda: ninja('-vC', build_dir, target)))

        wait_for_futures(futures)
コード例 #2
0
ファイル: gen-atlas.py プロジェクト: lvshiling/taisei
def gen_atlas(overrides,
              src,
              dst,
              binsize,
              atlasname,
              tex_format=texture_formats[0],
              border=1,
              force_single=False,
              crop=True,
              leanify=True):
    overrides = Path(overrides).resolve()
    src = Path(src).resolve()
    dst = Path(dst).resolve()

    sprite_configs = {}

    def get_border(sprite, default_border=border):
        return max(default_border,
                   int(sprite_configs[sprite].get('border', default_border)))

    try:
        texture_local_overrides = (src / 'atlas.tex').read_text()
    except FileNotFoundError:
        texture_local_overrides = None

    try:
        texture_global_overrides = (overrides / 'atlas.tex').read_text()
    except FileNotFoundError:
        texture_global_overrides = None

    total_images = 0
    packed_images = 0
    rects = []

    def imgfilter(path):
        return (path.is_file() and path.suffix[1:].lower() in texture_formats
                and path.with_suffix('').suffix != '.alphamap')

    for path in sorted(filter(imgfilter, src.glob('**/*.*'))):
        img = Image.open(path)
        sprite_name = path.relative_to(src).with_suffix('').as_posix()
        sprite_config_path = overrides / (get_override_file_name(sprite_name) +
                                          '.conf')
        sprite_configs[sprite_name] = parse_sprite_conf(sprite_config_path)
        border = get_border(sprite_name)
        rects.append((img.size[0] + border * 2, img.size[1] + border * 2,
                      (path, sprite_name)))
        img.close()

    pack_result = pack_rects_brute_force(rects=rects,
                                         bin_size=binsize,
                                         single_bin=force_single)

    if not pack_result.success:
        missing = len(rects) - pack_result.num_images_packed
        raise TaiseiError(
            f'{missing} sprite{"s were" if missing > 1 else " was"} not packed (bin size is too small?)'
        )

    futures = []

    with ExitStack() as stack:
        # Do everything in a temporary directory first
        temp_dst = Path(
            stack.enter_context(
                TemporaryDirectory(prefix=f'taisei-atlas-{atlasname}')))

        # Run multiple leanify processes in parallel, in case we end up with multiple pages
        # Yeah I'm too lazy to use Popen properly
        executor = stack.enter_context(ThreadPoolExecutor())

        for i, bin in enumerate(pack_result.packer):
            textureid = f'atlas_{atlasname}_{i}'
            # dstfile = temp_dst / f'{textureid}.{tex_format}'
            # NOTE: we always save PNG first and convert with an external tool later if needed.
            dstfile = temp_dst / f'{textureid}.png'
            dstfile_alphamap = temp_dst / f'{textureid}.alphamap.png'
            print(dstfile)

            dstfile_meta = temp_dst / f'{textureid}.tex'

            actual_size = [0, 0]

            if crop:
                for rect in bin:
                    if rect.x + rect.width > actual_size[0]:
                        actual_size[0] = rect.x + rect.width

                    if rect.y + rect.height > actual_size[1]:
                        actual_size[1] = rect.y + rect.height
            else:
                actual_size = (bin.width, bin.height)

            base_composite_cmd = [
                'convert',
                '-verbose',
                '-size',
                f'{actual_size[0]}x{actual_size[1]}',
            ]

            composite_cmd = base_composite_cmd.copy() + ['xc:none']
            alphamap_composite_cmd = None

            for rect in bin:
                img_path, name = rect.rid
                border = get_border(name)
                alphamap_path = find_alphamap(img_path)

                composite_cmd += [
                    str(img_path), '-geometry',
                    '{:+}{:+}'.format(rect.x + border,
                                      rect.y + border), '-composite'
                ]

                if alphamap_path:
                    if alphamap_composite_cmd is None:
                        alphamap_composite_cmd = base_composite_cmd.copy() + [
                            'xc:white',
                            '-colorspace',
                            'Gray',
                        ]

                    alphamap_composite_cmd += [
                        str(alphamap_path),
                        '-geometry',
                        '{:+}{:+}'.format(rect.x + border, rect.y + border),
                        '-channel',
                        'R',
                        '-separate',
                        '-composite',
                    ]

                override_path = overrides / get_override_file_name(name)

                if override_path.exists():
                    override_contents = override_path.read_text()
                else:
                    override_contents = None
                    write_override_template(
                        override_path,
                        (rect.width - border * 2, rect.height - border * 2))

                write_sprite_def(
                    temp_dst / f'{name}.spr',
                    textureid,
                    (rect.x + border, rect.y + border, rect.width - border * 2,
                     rect.height - border * 2),
                    actual_size,
                    overrides=override_contents)

            composite_cmd += [str(dstfile)]

            write_texture_def(dstfile_meta,
                              textureid,
                              tex_format,
                              texture_global_overrides,
                              texture_local_overrides,
                              alphamap_tex_fmt=None
                              if alphamap_composite_cmd is None else 'png')

            def process(composite_cmd, dstfile, tex_format=tex_format):
                subprocess.check_call(composite_cmd)

                oldfmt = dstfile.suffix[1:].lower()

                if oldfmt != tex_format:
                    new_dstfile = dstfile.with_suffix(f'.{tex_format}')

                    if tex_format == 'webp':
                        subprocess.check_call([
                            'cwebp',
                            '-progress',
                            '-preset',
                            'icon',
                            '-z',
                            '9',
                            '-lossless',
                            '-q',
                            '100',
                            '-m',
                            '6',
                            str(dstfile),
                            '-o',
                            str(new_dstfile),
                        ])
                    else:
                        raise TaiseiError(
                            f'Unhandled conversion {oldfmt} -> {tex_format}')

                    dstfile.unlink()
                    dstfile = new_dstfile

                if leanify:
                    subprocess.check_call(['leanify', '-v', str(dstfile)])

            futures.append(
                executor.submit(lambda: process(composite_cmd, dstfile)))

            if alphamap_composite_cmd is not None:
                alphamap_composite_cmd += [str(dstfile_alphamap)]
                futures.append(
                    executor.submit(lambda: process(alphamap_composite_cmd,
                                                    dstfile_alphamap,
                                                    tex_format='png')))

        # Wait for subprocesses to complete.
        wait_for_futures(futures)
        executor.shutdown(wait=True)

        # Only now, if everything is ok so far, copy everything to the destination, possibly overwriting previous results
        pattern = re.compile(
            rf'^atlas_{re.escape(atlasname)}_\d+(?:\.alphamap)?.({"|".join(texture_formats + ["tex"])})$'
        )
        for path in dst.iterdir():
            if pattern.match(path.name):
                path.unlink()

        targets = list(temp_dst.glob('**/*'))

        for dir in (p.relative_to(temp_dst) for p in targets if p.is_dir()):
            (dst / dir).mkdir(parents=True, exist_ok=True)

        for file in (p.relative_to(temp_dst) for p in targets
                     if not p.is_dir()):
            shutil.copyfile(str(temp_dst / file), str(dst / file))
コード例 #3
0
ファイル: gen-atlas.py プロジェクト: tomoyosiki/taisei
def gen_atlas(overrides,
              src,
              dst,
              binsize,
              atlasname,
              tex_format=texture_formats[0],
              border=1,
              force_single=False,
              crop=True,
              leanify=True):
    overrides = Path(overrides).resolve()
    src = Path(src).resolve()
    dst = Path(dst).resolve()

    sprite_configs = {}

    def get_border(sprite, default_border=border):
        return max(default_border,
                   int(sprite_configs[sprite].get('border', default_border)))

    try:
        texture_local_overrides = (src / 'atlas.tex').read_text()
    except FileNotFoundError:
        texture_local_overrides = None

    try:
        texture_global_overrides = (overrides / 'atlas.tex').read_text()
    except FileNotFoundError:
        texture_global_overrides = None

    total_images = 0
    packed_images = 0
    rects = []

    for path in src.glob('**/*.*'):
        if path.is_file() and path.suffix[1:].lower() in texture_formats:
            img = Image.open(path)
            sprite_name = path.relative_to(src).with_suffix('').as_posix()
            sprite_config_path = overrides / (
                get_override_file_name(sprite_name) + '.conf')
            sprite_configs[sprite_name] = parse_sprite_conf(sprite_config_path)
            border = get_border(sprite_name)
            rects.append((img.size[0] + border * 2, img.size[1] + border * 2,
                          (path, sprite_name)))
            img.close()

    total_images = len(rects)

    make_packer = lambda: rectpack.newPacker(
        # No rotation support in Taisei yet
        rotation=False,

        # Fine-tuned for least area used after crop
        sort_algo=rectpack.SORT_SSIDE,
        bin_algo=rectpack.PackingBin.BFF,
        pack_algo=rectpack.MaxRectsBl,
    )

    binsize = list(binsize)

    if force_single:
        while True:
            packer = make_packer()
            packer.add_bin(*binsize)

            for rect in rects:
                packer.add_rect(*rect)

            packer.pack()

            if sum(len(bin) for bin in packer) == total_images:
                break

            if binsize[1] < binsize[0]:
                binsize[1] *= 2
            else:
                binsize[0] *= 2
    else:
        packer = make_packer()

        for rect in rects:
            packer.add_rect(*rect)
            packer.add_bin(*binsize)

        packer.pack()

    packed_images = sum(len(bin) for bin in packer)

    if total_images != packed_images:
        missing = total_images - packed_images
        raise TaiseiError(
            f'{missing} sprite{"s were" if missing > 1 else " was"} not packed (bin size is too small?)'
        )

    futures = []

    with ExitStack() as stack:
        # Do everything in a temporary directory first
        temp_dst = Path(
            stack.enter_context(
                TemporaryDirectory(prefix=f'taisei-atlas-{atlasname}')))

        # Run multiple leanify processes in parallel, in case we end up with multiple pages
        # Yeah I'm too lazy to use Popen properly
        executor = stack.enter_context(ThreadPoolExecutor())

        for i, bin in enumerate(packer):
            textureid = f'atlas_{atlasname}_{i}'
            # dstfile = temp_dst / f'{textureid}.{tex_format}'
            # NOTE: we always save PNG first and convert with an external tool later if needed.
            dstfile = temp_dst / f'{textureid}.png'
            print(dstfile)

            dstfile_meta = temp_dst / f'{textureid}.tex'
            write_texture_def(dstfile_meta, textureid, tex_format,
                              texture_global_overrides,
                              texture_local_overrides)

            actual_size = [0, 0]

            if crop:
                for rect in bin:
                    if rect.x + rect.width > actual_size[0]:
                        actual_size[0] = rect.x + rect.width

                    if rect.y + rect.height > actual_size[1]:
                        actual_size[1] = rect.y + rect.height
            else:
                actual_size = (bin.width, bin.height)

            composite_cmd = [
                'convert',
                '-verbose',
                '-size',
                f'{actual_size[0]}x{actual_size[1]}',
                'xc:none',
            ]

            for rect in bin:
                img_path, name = rect.rid
                border = get_border(name)

                composite_cmd += [
                    str(img_path), '-geometry',
                    '{:+}{:+}'.format(rect.x + border,
                                      rect.y + border), '-composite'
                ]

                override_path = overrides / get_override_file_name(name)

                if override_path.exists():
                    override_contents = override_path.read_text()
                else:
                    override_contents = None
                    write_override_template(override_path, img.size)

                write_sprite_def(
                    temp_dst / f'{name}.spr',
                    textureid,
                    (rect.x + border, rect.y + border, rect.width - border * 2,
                     rect.height - border * 2),
                    img.size,
                    overrides=override_contents)

            composite_cmd += [str(dstfile)]

            @executor.submit
            def process(dstfile=dstfile):
                subprocess.check_call(composite_cmd)

                oldfmt = dstfile.suffix[1:].lower()

                if oldfmt != tex_format:
                    new_dstfile = dstfile.with_suffix(f'.{tex_format}')

                    if tex_format == 'webp':
                        subprocess.check_call([
                            'cwebp',
                            '-progress',
                            '-preset',
                            'drawing',
                            '-z',
                            '9',
                            '-lossless',
                            '-q',
                            '100',
                            str(dstfile),
                            '-o',
                            str(new_dstfile),
                        ])
                    else:
                        raise TaiseiError(
                            f'Unhandled conversion {oldfmt} -> {tex_format}')

                    dstfile.unlink()
                    dstfile = new_dstfile

                if leanify:
                    subprocess.check_call(['leanify', '-v', str(dstfile)])

            futures.append(process)

        # Wait for subprocesses to complete.
        wait_for_futures(futures)
        executor.shutdown(wait=True)

        # Only now, if everything is ok so far, copy everything to the destination, possibly overwriting previous results
        pattern = re.compile(
            rf'^atlas_{re.escape(atlasname)}_\d+.({"|".join(texture_formats + ["tex"])})$'
        )
        for path in dst.iterdir():
            if pattern.match(path.name):
                path.unlink()

        targets = list(temp_dst.glob('**/*'))

        for dir in (p.relative_to(temp_dst) for p in targets if p.is_dir()):
            (dst / dir).mkdir(parents=True, exist_ok=True)

        for file in (p.relative_to(temp_dst) for p in targets
                     if not p.is_dir()):
            shutil.copyfile(str(temp_dst / file), str(dst / file))