Exemple #1
0
def test_open():
    with WalImageFile.open(TEST_FILE) as im:
        assert im.format == "WAL"
        assert im.format_description == "Quake2 Texture"
        assert im.mode == "P"
        assert im.size == (128, 128)

        assert isinstance(im, WalImageFile.WalImageFile)

        assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
Exemple #2
0
    def test_open(self):
        # Arrange
        TEST_FILE = "Tests/images/hopper.wal"

        # Act
        im = WalImageFile.open(TEST_FILE)

        # Assert
        self.assertEqual(im.format, "WAL")
        self.assertEqual(im.format_description, "Quake2 Texture")
        self.assertEqual(im.mode, "P")
        self.assertEqual(im.size, (128, 128))
Exemple #3
0
    def test_open(self):
        # Arrange
        TEST_FILE = "Tests/images/hopper.wal"

        # Act
        im = WalImageFile.open(TEST_FILE)

        # Assert
        self.assertEqual(im.format, "WAL")
        self.assertEqual(im.format_description, "Quake2 Texture")
        self.assertEqual(im.mode, "P")
        self.assertEqual(im.size, (128, 128))
Exemple #4
0
def test_open():
    # Arrange
    TEST_FILE = "Tests/images/hopper.wal"

    # Act
    im = WalImageFile.open(TEST_FILE)

    # Assert
    assert im.format == "WAL"
    assert im.format_description == "Quake2 Texture"
    assert im.mode == "P"
    assert im.size == (128, 128)
Exemple #5
0
def load_texture(pball_path: str, texture: str) -> Optional[Image.Image]:
    """
    Loads Image object based on texture name and subdir
    :param pball_path: path to game media folder
    :param texture: texture name the way it is stored in the bsp file (relative to pball/textures and without extension)
    :return: RGBA Image object
    """
    if not os.path.exists(pball_path + "/textures/" +
                          "/".join(texture.lower().split("/")[:-1])):
        print(
            f"Info: no such path {pball_path + '/textures/' + '/'.join(texture.lower().split('/')[:-1])}"
        )
        return
    # list of all files in stored subdirectory
    texture_options = os.listdir(pball_path + "/textures/" +
                                 "/".join(texture.lower().split("/")[:-1]))
    texture_path = ""
    # iterate through texture options until one name matches stored texture name
    for idx, tex_option in enumerate(texture_options):
        if texture.split("/")[-1].lower() == os.path.splitext(tex_option)[0]:
            texture_path = "/".join(
                texture.lower().split("/")[:-1]) + "/" + tex_option
            break

    # texture was not found in specified subdirectory
    if not texture_path:
        print("Missing texture: ", texture)
        return

    if os.path.splitext(texture_path)[1] in [".png", ".jpg", ".tga"]:
        img = Image.open(pball_path + "/textures/" + texture_path)
        img2 = img.convert("RGBA")
        return img2

    elif os.path.splitext(texture_path)[1] == ".wal":
        # wal files are 8 bit and require a palette
        with open("pb2e.pal", "r") as pal:
            conts = (pal.read().split("\n")[3:])
            conts = [b.split(" ") for b in conts]
            conts = [c for b in conts for c in b]
            conts.pop(len(conts) - 1)
            conts = list(map(int, conts))
            img3 = WalImageFile.open(pball_path + "/textures/" + texture_path)
            img3.putpalette(conts)
            img3 = img3.convert("RGBA")
            return img3
    else:
        print(
            f"Error: unsupported format {os.path.splitext(texture_path)[1]} in {texture_path}"
            f"\nsupported formats are .png, .jpg, .tga, .wal")
        return
Exemple #6
0
def get_polygons(path: str,
                 pball_path: str) -> Tuple[List[Polygon], List[Tuple[int]]]:
    """
    Converts information from Q2BSP object into List of Polygon objects
    Calculates mean color of all used textures and builds list of all unique colors
    :param path: full path to map
    :param pball_path: path to pball / game media directory, needed to get full texture path
    :return: list of Polygon objects, list of RGB colors
    """
    # instead of directly reading all information from file, the Q2BSP class is used for reading
    temp_map = Q2BSP(path)

    # get a list of unique texture names (which are stored without an extension -> multiple ones must be tested)
    texture_list = [x.get_texture_name() for x in temp_map.tex_infos]
    texture_list_cleaned = list(dict.fromkeys(texture_list))
    # iterate through texture list, look which one exists, load, rescale to 1×1 pixel = color is mean color
    average_colors = list()
    skip_faces = list()

    for texture in texture_list_cleaned:
        color = (0, 0, 0)
        if not os.path.exists(pball_path + "/textures/" +
                              "/".join(texture.lower().split("/")[:-1])):
            print(
                f"Info: no such path {pball_path+'/textures/'+'/'.join(texture.lower().split('/')[:-1])}"
            )
            # sets (0,0,0) as default color for missing textures
            average_colors.append((0, 0, 0))
            continue

        # list of all files in stored subdirectory
        texture_options = os.listdir(pball_path + "/textures/" +
                                     "/".join(texture.lower().split("/")[:-1]))
        texture_path = ""
        # iterate through texture options until one name matches stored texture name
        for idx, tex_option in enumerate(texture_options):
            if texture.split("/")[-1].lower() == os.path.splitext(
                    tex_option)[0]:
                texture_path = "/".join(
                    texture.lower().split("/")[:-1]) + "/" + tex_option
                break

        # texture was not found in specified subdirectory
        if not texture_path:
            print("Missing texture: ", texture)
            average_colors.append((0, 0, 0))
            continue

        if os.path.splitext(texture_path)[1] in [".png", ".jpg", ".tga"]:
            img = Image.open(pball_path + "/textures/" + texture_path)
            img2 = img.resize((1, 1))
            img2 = img2.convert("RGBA")
            img2 = img2.load()
            color = img2[0, 0]

        elif os.path.splitext(texture_path)[1] == ".wal":
            # wal files are 8 bit and require a palette
            with open("pb2e.pal", "r") as pal:
                conts = (pal.read().split("\n")[3:])
                conts = [b.split(" ") for b in conts]
                conts = [c for b in conts for c in b]
                conts.pop(len(conts) - 1)
                conts = list(map(int, conts))
                img3 = WalImageFile.open(pball_path + "/textures/" +
                                         texture_path)
                img3.putpalette(conts)
                img3 = img3.convert("RGBA")

                img2 = img3.resize((1, 1))

                color = img2.getpixel((0, 0))
        else:
            print(
                f"Error: unsupported format {os.path.splitext(texture_path)[1]} in {texture_path}"
                f"\nsupported formats are .png, .jpg, .tga, .wal")

        color_rgb = color[:3]
        if color_rgb == (0, 0, 0):
            print(texture)
        if True in [
                x in texture.lower()
                for x in ["origin", "clip", "skip", "hint", "trigger"]
        ]:
            print(texture)
            color_rgb = (0, 0, 0, 0)  # actually rgba
        average_colors.append(color_rgb)

    # instead of storing face color directly in the Polygon object, store an index so that you can easily change one
    # color for all faces using the same one
    tex_indices = [x.texture_info for x in temp_map.faces]
    tex_ids = [
        texture_list_cleaned.index(texture_list[tex_index])
        for tex_index in tex_indices
    ]

    # each face is a list of vertices stored as Tuples
    faces: List[List[Tuple]] = list()
    skip_surfaces = []
    for idx, face in enumerate(temp_map.faces):
        flags = temp_map.tex_infos[face.texture_info].flags
        if flags.hint or flags.nodraw or flags.sky or flags.skip:
            skip_surfaces.append(idx)
        current_face: List[Tuple] = list()
        for i in range(face.num_edges):
            face_edge = temp_map.face_edges[face.first_edge + i]
            if face_edge > 0:
                edge = temp_map.edge_list[face_edge]
            else:
                edge = temp_map.edge_list[abs(face_edge)][::-1]
            for vert in edge:
                if not temp_map.vertices[vert] in current_face:
                    current_face.append(temp_map.vertices[vert])
        faces.append(current_face)

    # get minimal of all x y and z values and move all vertices so they all have coordinate values >= 0
    min_x = min([a[0] for b in faces for a in b])
    min_y = min([a[1] for b in faces for a in b])
    min_z = min([a[2] for b in faces for a in b])

    polys_normalized = [[[
        vertex[0] - min_x, vertex[1] - min_y, vertex[2] - min_z
    ] for vertex in edge] for edge in faces]

    # get normals out of the Q2BSP object, if face.plane_side != 0, flip it (invert signs of coordinates)
    normal_list = [x.normal for x in temp_map.planes]
    normals = list()
    for face in temp_map.faces:
        # print(temp_map.tex_infos[face.texture_info].flags)
        if not face.plane_side == 0:
            # -1*0.0 returns -0.0 which is prevented by this expression
            # TODO: Does -0.0 do any harm here?
            normal = [
                -1 * x if not x == 0.0 else x for x in normal_list[face.plane]
            ]
        else:
            normal = list(normal_list[face.plane])
        normals.append(normal)

    # construct polygon list out of the faces, indices into unique textures aka colors (two different textures could
    # have the same mean color), normals
    polygons: List[Polygon] = list()
    for idx, poly in enumerate(polys_normalized):
        polygon = Polygon(poly, tex_ids[idx], point3f(*normals[idx]))
        polygons.append(polygon)

    print(skip_surfaces, "skip")
    for i in skip_surfaces[::-1]:
        polygons.pop(i)

    # for idx, poly in enumerate(polygons):
    #     print(average_colors[poly.tex_id])
    #     if average_colors[poly.tex_id] == (0,0,0,0):
    #         print("here")
    #         polygons.pop(len(polygons)-1-idx)

    return polygons, average_colors
def get_polys(path, pball_path):
    with open(path, "rb") as f:  # bsps are binary files
        bytes1 = f.read()  # stores all bytes in bytes1 variable (named like that to not interfere with builtin names
        # get offset (position of entity block begin) and length of entity block -> see bsp quake 2 format documentation
        offset_faces = int.from_bytes(bytes1[56:60], byteorder='little', signed=False)
        length_faces = int.from_bytes(bytes1[60:64], byteorder='little', signed=False)

        offset_verts = int.from_bytes(bytes1[24:28], byteorder='little', signed=False)
        length_verts = int.from_bytes(bytes1[28:32], byteorder='little', signed=False)
        vertices = list()
        for i in range(int(length_verts / 12)):
            (vert_x,) = struct.unpack('<f', (bytes1[offset_verts + 12 * i + 0:offset_verts + 12 * i + 4]))
            (vert_y,) = struct.unpack('<f', (bytes1[offset_verts + 12 * i + 4:offset_verts + 12 * i + 8]))
            (vert_z,) = struct.unpack('<f', (bytes1[offset_verts + 12 * i + 8:offset_verts + 12 * i + 12]))
            vertices.append([vert_x, vert_y, vert_z])
            # print(f"{vert_x} - {vert_y} - {vert_z}")

        offset_edges = int.from_bytes(bytes1[96:100], byteorder='little', signed=False)
        length_edges = int.from_bytes(bytes1[100:104], byteorder='little', signed=False)
        edges = list()
        for i in range(int(length_edges / 4)):  # texture information lump is 76 bytes large
            vert_1 = int.from_bytes(bytes1[offset_edges + 4 * i + 0:offset_edges + 4 * i + 2], byteorder='little', signed=False)
            vert_2 = int.from_bytes(bytes1[offset_edges + 4 * i + 2:offset_edges + 4 * i + 4], byteorder='little', signed=False)
            edges.append([vertices[vert_1], vertices[vert_2]])

        offset_face_edges = int.from_bytes(bytes1[104:108], byteorder='little', signed=False)
        length_face_edges = int.from_bytes(bytes1[108:112], byteorder='little', signed=False)
        face_edges = list()
        for i in range(int(length_face_edges / 4)):  # texture information lump is 76 bytes large
            edge_index = int.from_bytes(bytes1[offset_face_edges + 4 * i + 0:offset_face_edges + 4 * i + 4], byteorder='little', signed=True)
            if edge_index > 0:
                face_edges.append([edges[abs(edge_index)][0], edges[abs(edge_index)][1]])
            elif edge_index < 0:
                face_edges.append([edges[abs(edge_index)][1], edges[abs(edge_index)][0]])

        offset_textures = int.from_bytes(bytes1[48:52], byteorder='little', signed=False)
        length_textures = int.from_bytes(bytes1[52:56], byteorder='little', signed=False)
        texture_list = list()
        for i in range(int(length_textures/76)):
            tex = (bytes1[offset_textures+76*i+40:offset_textures+76*i+72])
            tex = [x for x in tex if x]
            tex_name = struct.pack("b" * len(tex), *tex).decode('ascii', "ignore")
            print(tex_name)
            texture_list.append(tex_name)

        faces = list()
        tex_ids = list()
        texture_list_cleaned=list(dict.fromkeys(texture_list))

        average_colors=list()
        for texture in texture_list_cleaned:
            color = (0, 0, 0)
            if os.path.isfile(pball_path+"/textures/"+texture+".png"):
                img = Image.open((pball_path+"/textures/"+texture+".png"))
                img2 = img.resize((1, 1))

                color = img2.getpixel((0, 0))

            elif os.path.isfile(pball_path+"/textures/"+texture+".jpg"):
                img = Image.open((pball_path+"/textures/"+texture+".jpg"))
                img.save("1.png")
                img2 = img.resize((1, 1))
                # break

                color = img2.getpixel((0, 0))
                # print(f"texture: {texture} - color: {color}")

            elif os.path.isfile(pball_path + "/textures/" + texture + ".tga"):
                img = Image.open((pball_path + "/textures/" + texture + ".tga"))
                img2 = img.resize((1, 1))

                color = img2.getpixel((0, 0))
                # print(f"texture: {texture} - color: {color}")

            elif os.path.isfile(pball_path+"/textures/"+texture+".wal"):
                with open("pb2e.pal", "r") as pal:
                    conts = (pal.read().split("\n")[3:])
                    conts = [b.split(" ") for b in conts]
                    conts = [c for b in conts for c in b]
                    conts.pop(len(conts)-1)
                    conts=list(map(int, conts))
                    img3 = WalImageFile.open((pball_path+"/textures/"+texture+".wal"))
                    img3.putpalette(conts)
                    img3=img3.convert("RGBA")
                    print(img3.mode)

                    img2 = img3.resize((1, 1))

                    color = img2.getpixel((0, 0))
            print(f"texture: {texture} - color: {color}")
            color_rgb = color[:3]
            average_colors.append(color_rgb)

        for i in range(int(length_faces / 20)):  # texture information lump is 76 bytes large
            # get sum of flags / transform flag bit field to uint32
            first_edge = (bytes1[offset_faces + 20 * i + 4:offset_faces + 20 * i + 8])
            (num_edges,) = struct.unpack('<H', (bytes1[offset_faces + 20 * i + 8:offset_faces + 20 * i + 10]))
            (tex_index,) = struct.unpack('<H', (bytes1[offset_faces + 20 * i + 10:offset_faces + 20 * i + 12]))
            print(tex_index)
            tex_ids.append(texture_list_cleaned.index(texture_list[tex_index]))
            print(tex_ids[len(tex_ids)-1])
            first_edge = int.from_bytes(first_edge, byteorder='little', signed=True)
            next_edges = list()
            for j in range(num_edges):
                if face_edges[first_edge+j][0] not in next_edges:
                    next_edges.append(face_edges[first_edge+j][0])

                if face_edges[first_edge + j][1] not in next_edges:
                    next_edges.append(face_edges[first_edge + j][1])
            faces.append(next_edges)

        print(texture_list_cleaned)
        print(tex_ids)
        print(average_colors)

        min_x = min([p for i in [[vertex[0] for vertex in edge] for edge in faces] for p in i])
        min_y = min([p for i in [[vertex[1] for vertex in edge] for edge in faces] for p in i])
        min_z = min([p for i in [[vertex[2] for vertex in edge] for edge in faces] for p in i])

        return [[[round(vertex[0]-min_x), round(vertex[1]-min_y), round(vertex[2]-min_z)] for vertex in edge] for edge in faces], tex_ids, average_colors
Exemple #8
0
def test_load():
    with WalImageFile.open(TEST_FILE) as im:
        assert im.load()[0, 0] == 122

        # Test again now that it has already been loaded once
        assert im.load()[0, 0] == 122