Exemplo n.º 1
0
    def from_pil(self,
                 bpc: Bpc,
                 bpl: Bpl,
                 lower_img: Image.Image = None,
                 upper_img: Image.Image = None,
                 force_import=False,
                 how_many_palettes_lower_layer=16):
        """
        Import an entire map from one or two images (for each layer).
        Changes all tiles, tilemappings and chunks in the BPC and re-writes the two layer mappings of the BMA.
        Imports the palettes of the image to the BPL.
        The palettes of the images passed into this method must either identical or can be merged.
        The how_many_palettes_lower_layer parameter controls how many palettes
        from the lower layer image will then be used.

        The passed PIL will be split into separate tiles and the tile's palette index in the tile mapping for this
        coordinate is determined by the first pixel value of each tile in the PIL. The PIL
        must have a palette containing up to 16 sub-palettes with 16 colors each (256 colors).

        If a pixel in a tile uses a color outside of it's 16 color range, an error is thrown or
        the color is replaced with 0 of the palette (transparent). This is controlled by
        the force_import flag.

        Does not import animations. BPA tiles must be manually mapped to the tilemappings of the BPC after the import.
        BPL palette animations are not modified.

        The input images must have the same dimensions as the BMA (same dimensions as to_pil_single_layer would export).
        The input image can have a different number of layers, than the BMA. BPC and BMA layers are changed accordingly.

        BMA collision and data layer are not modified.
        """
        expected_width = self.tiling_width * self.map_width_chunks * BPC_TILE_DIM
        expected_height = self.tiling_height * self.map_height_chunks * BPC_TILE_DIM
        if (False if lower_img is None else lower_img.width != expected_width) \
                or (False if upper_img is None else upper_img.width != expected_width):
            raise ValueError(
                f"Can not import map background: Width of both images must match the current map width: "
                f"{expected_width}px")
        if (False if lower_img is None else lower_img.height != expected_height) \
                or (False if upper_img is None else upper_img.height != expected_height):
            raise ValueError(
                f"Can not import map background: Height of both images must match the current map height: "
                f"{expected_height}px")
        upper_palette_palette_color_offset = 0
        if upper_img is not None and lower_img is not None and how_many_palettes_lower_layer < BPL_MAX_PAL:
            # Combine palettes
            lower_palette = lower_img.getpalette(
            )[:how_many_palettes_lower_layer * (BPL_PAL_LEN + 1) * 3]
            upper_palette = upper_img.getpalette()[:(
                BPL_MAX_PAL - how_many_palettes_lower_layer) *
                                                   (BPL_PAL_LEN + 1) * 3]
            new_palette = lower_palette + upper_palette
            lower_img.putpalette(new_palette)
            upper_img.putpalette(new_palette)
            # We need to offset the colors in the upper image now, when we read it.
            upper_palette_palette_color_offset = how_many_palettes_lower_layer

        # Adjust layer numbers
        number_of_layers = 2 if upper_img is not None else 1
        low_map_idx = 0 if lower_img is not None else 1
        if number_of_layers > self.number_of_layers:
            self.add_upper_layer()
            bpc.add_upper_layer()

        # Import tiles, tile mappings and chunks mappings
        for layer_idx in range(low_map_idx, number_of_layers):
            if layer_idx == 0:
                bpc_layer_id = 0 if bpc.number_of_layers == 1 else 1
                img = lower_img
                palette_offset = 0
            else:
                bpc_layer_id = 0
                img = upper_img
                palette_offset = upper_palette_palette_color_offset

            tiles, all_possible_tile_mappings, palettes = from_pil(
                img,
                BPL_IMG_PAL_LEN,
                BPL_MAX_PAL,
                BPC_TILE_DIM,
                img.width,
                img.height,
                3,
                3,
                force_import,
                palette_offset=palette_offset)
            bpc.import_tiles(bpc_layer_id, tiles)

            # Build a new list of chunks / tile mappings for the BPC based on repeating chunks
            # in the imported image. Generate chunk mappings.
            chunk_mappings = []
            chunk_mappings_counter = 1
            tile_mappings = []
            tiles_in_chunk = self.tiling_width * self.tiling_height
            for chk_fst_tile_idx in range(
                    0, self.map_width_chunks * self.map_height_chunks *
                    tiles_in_chunk, tiles_in_chunk):
                chunk = all_possible_tile_mappings[
                    chk_fst_tile_idx:chk_fst_tile_idx + tiles_in_chunk]
                start_of_existing_chunk = search_for_chunk(
                    chunk, tile_mappings)
                if start_of_existing_chunk is not None:
                    chunk_mappings.append(
                        int(start_of_existing_chunk / tiles_in_chunk) + 1)
                else:
                    tile_mappings += chunk
                    chunk_mappings.append(chunk_mappings_counter)
                    chunk_mappings_counter += 1

            bpc.import_tile_mappings(bpc_layer_id, tile_mappings)
            if layer_idx == 0:
                self.layer0 = chunk_mappings
            else:
                self.layer1 = chunk_mappings

        # Import palettes
        bpl.import_palettes(palettes)
Exemplo n.º 2
0
    def from_pil(self,
                 dpc: Dpc,
                 dpci: Dpci,
                 dpl: Dpl,
                 img: Image.Image,
                 force_import: bool = False) -> None:
        """
        Import an entire background from an image.
        Changes all tiles, tile mappings and chunks in the DPC/DPCI and re-writes the mappings of the DBG.
        Imports the palettes of the image to the DPL.

        The passed PIL will be split into separate tiles and the tile's palette index in the tile mapping for this
        coordinate is determined by the first pixel value of each tile in the PIL. The PIL
        must have a palette containing up to 16 sub-palettes with 16 colors each (256 colors).

        If a pixel in a tile uses a color outside of it's 16 color range, an error is thrown or
        the color is replaced with 0 of the palette (transparent). This is controlled by
        the force_import flag.

        The input images must have the same dimensions as the DBG (same dimensions as to_pil_single_layer would export).
        """
        expected_width = DBG_TILING_DIM * DBG_WIDTH_AND_HEIGHT * DPCI_TILE_DIM
        expected_height = DBG_TILING_DIM * DBG_WIDTH_AND_HEIGHT * DPCI_TILE_DIM
        if img.width != expected_width:
            raise UserValueError(
                f(
                    _("Can not import map background: Width of image must match the expected width: "
                      "{expected_width}px")))
        if img.height != expected_height:
            raise UserValueError(
                f(
                    _("Can not import map background: Height of image must match the expected height: "
                      "{expected_height}px")))

        # Import tiles, tile mappings and chunks mappings
        tiles, all_possible_tile_mappings, palettes = from_pil(
            img, DPL_PAL_LEN, 16, DPCI_TILE_DIM, img.width, img.height, 3, 3,
            force_import)
        # Remove any extra colors
        palettes = palettes[:DPL_MAX_PAL]

        dpci.import_tiles(tiles)

        # Build a new list of chunks / tile mappings for the DPC based on repeating chunks
        # in the imported image. Generate chunk mappings.
        chunk_mappings = []
        chunk_mappings_counter = 1
        tile_mappings: List[TilemapEntryProtocol] = []
        tiles_in_chunk = DBG_TILING_DIM * DBG_TILING_DIM
        for chk_fst_tile_idx in range(
                0,
                DBG_WIDTH_AND_HEIGHT * DBG_WIDTH_AND_HEIGHT * tiles_in_chunk,
                tiles_in_chunk):
            chunk = all_possible_tile_mappings[
                chk_fst_tile_idx:chk_fst_tile_idx + tiles_in_chunk]
            start_of_existing_chunk = search_for_chunk(chunk, tile_mappings)
            if start_of_existing_chunk is not None:
                chunk_mappings.append(
                    u16(int(start_of_existing_chunk / tiles_in_chunk) + 1))
            else:
                tile_mappings += chunk
                chunk_mappings.append(u16(chunk_mappings_counter))
                chunk_mappings_counter += 1

        dpc.import_tile_mappings(
            list(chunks(tile_mappings,
                        DPC_TILING_DIM * DPC_TILING_DIM)))  # type: ignore
        self.mappings = chunk_mappings

        # Import palettes
        dpl.palettes = palettes