Skip to content

Commit

Permalink
Merge pull request #173 from zao/feature/map-item-shade
Browse files Browse the repository at this point in the history
compose map icons like the game
  • Loading branch information
lvlvllvlvllvlvl authored Apr 7, 2024
2 parents 1beb0ae + 8ffcb36 commit 2c82bf2
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 33 deletions.
89 changes: 56 additions & 33 deletions PyPoE/cli/exporter/wiki/parsers/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ def gemshade_constants_from_hex(hex_text: str):
return GemShadeConstants(*struct.unpack("<ffff", buf))


def _srgb_to_linear(img):
return np.piecewise(
img,
[img < 0.04045, img >= 0.04045],
[lambda v: v / 12.92, lambda v: ((v + 0.055) / 1.055) ** 2.4],
)


def _linear_to_srgb(img):
return np.piecewise(
img,
[img < 0.0031308, img >= 0.0031308],
[lambda v: v * 12.92, lambda v: 1.055 * v ** (1.0 / 2.4) - 0.055],
)


def _apply_column_map(infobox, column_map: tuple[tuple[str, dict], ...], list_object):
for k, data in column_map:
value = list_object[k]
Expand Down Expand Up @@ -450,14 +466,10 @@ class ItemsParser(SkillParserShared):
)

_MAP_COLORS = {
"mid tier": "255,210,100",
"high tier": "240,30,10",
}

# Midpoint values are the luminosities of _MAP_COLORS entries
_MAP_COLOR_MIDPOINTS = {
"mid tier": 211,
"high tier": 91,
"low tier": (248, 248, 248),
"mid tier": (252, 159, 14),
"high tier": (235, 3, 0),
"purple tier": (131, 54, 231),
}

_MAP_RELEASE_VERSION = {
Expand Down Expand Up @@ -3902,20 +3914,6 @@ def _get_gem_icon_process(self, infobox: dict[str, str]):
attr = max(attrs, key=attrs.get)
var = infobox.pop("gem_shader")

def _srgb_to_linear(img):
return np.piecewise(
img,
[img < 0.04045, img >= 0.04045],
[lambda v: v / 12.92, lambda v: ((v + 0.055) / 1.055) ** 2.4],
)

def _linear_to_srgb(img):
return np.piecewise(
img,
[img < 0.0031308, img >= 0.0031308],
[lambda v: v * 12.92, lambda v: 1.055 * v ** (1.0 / 2.4) - 0.055],
)

def process(img: Image):
adorn = img.crop((0, 0, 78, 78))
base = img.crop((2 * 78, 0, 3 * 78, 78))
Expand Down Expand Up @@ -4056,6 +4054,7 @@ def export_map_icons(self, parsed_args):

# output base icon (without map symbol) to .../Base.dds
base_ico = os.path.join(self._img_path, "Base.dds")
purple_ico = os.path.join(self._img_path, "Tier17.dds")

# read from the file path in the BaseIcon_DDSFile field from MapSeries.dat.
self._write_dds(
Expand All @@ -4064,6 +4063,13 @@ def export_map_icons(self, parsed_args):
parsed_args=parsed_args,
)

# read from the file path in the Purple_DDSFile field from MapSeries.dat.
self._write_dds(
data=self.file_system.get_file(map_series["Purple_DDSFile"]),
out_path=purple_ico,
parsed_args=parsed_args,
)

# === Maps from Atlas ===
for atlas_node in self.rr["AtlasNode.dat64"]:
if not atlas_node["ItemVisualIdentityKey"]["DDSFile"]:
Expand All @@ -4090,17 +4096,20 @@ def export_map_icons(self, parsed_args):
if not os.path.isfile(ico_path):
continue

# Tint with tier color, this historically differs from the colorization
# used for composing map glyphs onto on the itemized map base.
img = Image.open(ico_path)
midpoint = self._MAP_COLOR_MIDPOINTS[name]
img = _colorize_rgba(
img, "black", "white", mid=f"rgb({color})", midpoint=midpoint
)
img = self._shade_sigil(img, color)
img.save(out_path)

return r

def _shade_sigil(self, tex, color):
color = np.reshape(np.array(color + (255,)), (1, 1, 4)) / 255.0
samples = np.asarray(tex, np.float32) / 255.0
tex_colour = np.dstack((_srgb_to_linear(samples[:, :, :3]), samples[:, :, 3]))
final = color * tex_colour
final[:, :, :3] = _linear_to_srgb(final[:, :, :3])
return Image.fromarray(np.uint8(final * 255.0), "RGBA")

def export_map(self, parsed_args):
r = ExporterResult()

Expand Down Expand Up @@ -4160,8 +4169,18 @@ def export_map(self, parsed_args):
parsed_args=parsed_args,
)

purple_ico = os.path.join(self._img_path, "Map purple icon.dds")

self._write_dds(
data=self.file_system.get_file(map_series["Purple_DDSFile"]),
out_path=purple_ico,
parsed_args=parsed_args,
)

base_ico = base_ico.replace(".dds", ".png")
base_img = Image.open(base_ico)
purple_ico = purple_ico.replace(".dds", ".png")
purple_img = Image.open(purple_ico)

self.rr["MapSeriesTiers.dat64"].build_index("MapsKey")
self.rr["MapPurchaseCosts.dat64"].build_index("Tier")
Expand Down Expand Up @@ -4318,28 +4337,32 @@ def export_map(self, parsed_args):
img = Image.open(ico)
img.save(ico)

plate_img = purple_img if 16 < starting_tier else base_img

# Recolor the map icon if appropriate and layer the map icon with the base icon.
if base_item_type["Id"] not in MAPS_TO_SKIP_COLORING:
color = None
if 5 < starting_tier <= 10:
color = self._MAP_COLORS["mid tier"]
if 10 < starting_tier:
color = self._MAP_COLORS["high tier"]
if 16 < starting_tier:
color = self._MAP_COLORS["purple tier"]

# This isn't quite how the game actually makes these map icons,
# so it isn't ideal, but it works.
if color:
img = _colorize_rgba(img, "black", f"rgb({color})")
img = self._shade_sigil(img, color)
img.save(ico)

if base_item_type["Id"] not in MAPS_TO_SKIP_COMPOSITING:
canvas = Image.new(base_img.mode, base_img.size, (0, 0, 0, 0))
canvas = Image.new(plate_img.mode, plate_img.size, (0, 0, 0, 0))
paste_origin = (
(base_img.size[0] - img.size[0]) // 2,
(base_img.size[1] - img.size[1]) // 2,
(plate_img.size[0] - img.size[0]) // 2,
(plate_img.size[1] - img.size[1]) // 2,
)
canvas.paste(img, paste_origin)
Image.alpha_composite(base_img, canvas).save(ico)
Image.alpha_composite(plate_img, canvas).save(ico)

return r

Expand Down
6 changes: 6 additions & 0 deletions PyPoE/poe/file/specification/data/generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -18523,6 +18523,12 @@
file_path=True,
file_ext=".dds",
),
Field(
name="Purple_DDSFile",
type="ref|string",
file_path=True,
file_ext=".dds",
),
),
),
"MapSeriesTiers.dat": File(
Expand Down

0 comments on commit 2c82bf2

Please sign in to comment.