renpy/launcher/game/gui7/images.py

432 lines
11 KiB
Python
Raw Normal View History

2023-01-18 22:13:55 +00:00
# Copyright 2004-2022 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import pygame_sdl2
import os
from renpy.store import config
import renpy.display
class ImageGenerator(object):
def __init__(self, parameters):
pygame_sdl2.image.init()
self.p = p = parameters
self.width = p.width
self.height = p.height
self.scale = p.scale
self.accent_color = p.accent_color
self.boring_color = p.boring_color
self.hover_color = p.hover_color
self.muted_color = p.muted_color
self.hover_muted_color = p.hover_muted_color
self.menu_color = p.menu_color
self.prefix = os.path.join(p.prefix, "gui", "")
try:
os.mkdir(self.prefix, 0o777)
except Exception:
pass
self.full_width = self.width / self.scale
self.full_height = self.height / self.scale
self.overwrite = p.replace_images
def scale_int(self, n):
rv = int(n * self.scale)
if rv < 1:
rv = 1
return rv
def rescale_template(self, t):
rv = [ ]
for pos, opacity in t:
rv.append((pos * self.scale, opacity))
return rv
def generate_line(self, template):
size = int(max(i[0] for i in template))
rv = [ ]
right_pos, right_value = template[0]
for i in range(size):
if i == right_pos:
rv.append(right_value)
continue
while i >= right_pos:
left_pos = right_pos
left_value = right_value
right_pos, right_value = template.pop(0)
done = 1.0 * (i - left_pos) / (right_pos - left_pos)
rv.append(left_value + done * (right_value - left_value))
return rv
def crop_line(self, line, size):
"""
Crops the center `size` pixels out of `line`.
"""
if len(line) <= size:
return line
start = (len(line) - size) // 2
return line[start:start + size ]
def save(self, s, filename, overwrite=True):
fn = self.prefix + filename + ".png"
dn = os.path.dirname(fn)
try:
os.makedirs(dn, 0o777)
except Exception:
pass
if os.path.exists(fn):
if not overwrite:
return
if not self.overwrite:
return
index = 1
while True:
bfn = u"{}.{}.bak".format(fn, index)
if not os.path.exists(bfn):
break
index += 1
if not self.p.skip_backup:
os.rename(fn, bfn)
pygame_sdl2.image.save(s, fn, 3)
def make_surface(self, width, height):
return pygame_sdl2.Surface((width, height), pygame_sdl2.SRCALPHA)
def generate_image(self, filename, xtmpl, ytmpl, color=(0, 0, 0, 255)):
r, g, b, a = color
xtmpl = self.rescale_template(xtmpl)
ytmpl = self.rescale_template(ytmpl)
xline = self.generate_line(xtmpl)
yline = self.generate_line(ytmpl)
xline = self.crop_line(xline, self.width)
yline = self.crop_line(yline, self.height)
s = self.make_surface(len(xline), len(yline))
for x, xv in enumerate(xline):
for y, yv in enumerate(yline):
v = xv * yv
s.set_at((x, y), (r, g, b, int(a * v)))
self.save(s, filename)
def generate_textbox(self):
XSIZE = self.full_width
XINSIDE = (XSIZE - 744) // 2
YSIZE = 185
YBORDER = 5
X = [
(0, 0.0),
(XINSIDE, 1.0),
(XSIZE - XINSIDE, 1.0),
(XSIZE, 0.0),
]
Y = [
(0, 0.0),
(YBORDER, 1.0),
(YSIZE, 1.0),
]
self.generate_image("textbox", X, Y, self.boring_color.opacity(.8))
YSIZE = 240
YBORDER = 5
X = [ (0, 1.0), (self.full_width, 1.0) ]
Y = [
(0, 0.0),
(YBORDER, 1.0),
(YSIZE, 1.0),
]
self.generate_image("phone/textbox", X, Y, self.boring_color.opacity(.8))
width = self.scale_int(300)
height = self.scale_int(36)
s = self.make_surface(width, height)
self.save(s, "namebox")
def generate_nvl(self):
XSIZE = self.full_width
XINSIDE = (XSIZE - 800) // 2
YSIZE = self.full_height
X = [
(0, 0.0),
(XINSIDE, 1.0),
(XSIZE - XINSIDE, 1.0),
(XSIZE, 0.0),
]
Y = [
(0, 1.0),
(YSIZE, 1.0),
]
self.generate_image("nvl", X, Y, self.boring_color.opacity(.8))
X = [
(0, 1.0),
(XSIZE, 1.0),
]
Y = [
(0, 1.0),
(YSIZE, 1.0),
]
self.generate_image("phone/nvl", X, Y, self.boring_color.opacity(.8))
def generate_choice_button(self):
XSIZE = 790
XINSIDE = 100
YSIZE = 35
YBORDER = 3
X = [
(0, 0.0),
(XINSIDE, 1.0),
(XSIZE - XINSIDE, 1.0),
(XSIZE, 0.0),
]
Y = [
(0, 0.0),
(YBORDER, 1.0),
(YSIZE - YBORDER, 1.0),
(YSIZE, 0.0),
]
self.generate_image("button/choice_idle_background", X, Y, self.boring_color.opacity(.8))
self.generate_image("button/choice_hover_background", X, Y, self.accent_color.opacity(.95))
self.generate_image("phone/button/choice_idle_background", X, Y, self.boring_color.opacity(.8))
self.generate_image("phone/button/choice_hover_background", X, Y, self.accent_color.opacity(.95))
def generate_overlay(self):
width = self.scale_int(280)
phone_width = self.scale_int(340)
game_y = self.scale_int(120)
game_height = self.scale_int(570)
line_width = self.scale_int(3)
if self.p.light:
opacity = 0.9
else:
opacity = 0.8
# Main menu.
mm = self.make_surface(self.width, self.height)
mm.subsurface((0, 0, width, self.height)).fill(self.boring_color.opacity(opacity))
mm.subsurface((width - line_width, 0, line_width, self.height)).fill(self.accent_color)
self.save(mm, "overlay/main_menu")
mm = self.make_surface(self.width, self.height)
mm.subsurface((0, 0, phone_width, self.height)).fill(self.boring_color.opacity(opacity))
mm.subsurface((phone_width - line_width, 0, line_width, self.height)).fill(self.accent_color)
self.save(mm, "phone/overlay/main_menu")
# Game menu.
gm = self.make_surface(self.width, self.height)
gm.fill(self.boring_color.opacity(opacity))
gm.subsurface((width - line_width, game_y, line_width, game_height)).fill(self.accent_color)
self.save(gm, "overlay/game_menu")
gm = self.make_surface(self.width, self.height)
gm.fill(self.boring_color.opacity(opacity))
gm.subsurface((phone_width - line_width, game_y, line_width, game_height)).fill(self.accent_color)
self.save(gm, "phone/overlay/game_menu")
# Confirm.
gm = self.make_surface(self.width, self.height)
gm.fill(self.boring_color.opacity(.6))
self.save(gm, "overlay/confirm")
def generate_frame(self):
width = self.scale_int(600)
height = self.scale_int(250)
border = self.scale_int(3)
s = self.make_surface(width, height)
s.fill(self.accent_color)
s.subsurface((border, border, width - 2 * border, height - 2 * border)).fill(self.boring_color)
self.save(s, "frame")
def generate_quick_buttons(self):
width = self.scale_int(100)
height = self.scale_int(30)
s = self.make_surface(width, height)
self.save(s, "button/quick_idle_background")
self.save(s, "button/quick_hover_background")
def generate_skip(self):
XSIZE = 240
XRIGHT = 50
YSIZE = 43
X = [
(0, 1.0),
(XSIZE - XRIGHT, 1.0),
(XSIZE, 0.0),
]
Y = [
(0, 1.0),
(YSIZE, 1.0),
]
self.generate_image("skip", X, Y, self.boring_color.opacity(.8))
def generate_notify(self):
XSIZE = 922
XRIGHT = 50
YSIZE = 43
X = [
(0, 1.0),
(XSIZE - XRIGHT, 1.0),
(XSIZE, 0.0),
]
Y = [
(0, 1.0),
(YSIZE, 1.0),
]
self.generate_image("notify", X, Y, self.boring_color.opacity(.8))
def generate_icon(self):
icon_fn = os.path.join(config.renpy_base, "launcher", "game", "gui7", "icon.png")
icon = pygame_sdl2.image.load(icon_fn)
width, height = icon.get_size()
surf = pygame_sdl2.Surface((width, height), pygame_sdl2.SRCALPHA)
ro, go, bo, _ao = tuple(self.accent_color)
ro -= 23
go -= 23
bo -= 23
for y in range(height):
for x in range(width):
r, g, b, a = icon.get_at((x, y))
r = max(0, min(r + ro, 255))
g = max(0, min(g + go, 255))
b = max(0, min(b + bo, 255))
surf.set_at((x, y), (r, g, b, a))
self.save(surf, "window_icon", overwrite=False)
def generate_menus(self):
s = self.make_surface(self.width, self.height)
s.fill(self.menu_color)
self.save(s, "main_menu", overwrite=False)
self.save(s, "game_menu", overwrite=False)
def generate_all(self):
self.generate_textbox()
self.generate_choice_button()
self.generate_overlay()
self.generate_frame()
self.generate_nvl()
self.generate_quick_buttons()
self.generate_skip()
self.generate_notify()
self.generate_menus()
self.generate_icon()
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("prefix")
ap.add_argument("width", type=int)
ap.add_argument("height", type=int)
args = ap.parse_args()
ImageGenerator(args.prefix, args.width, args.height).generate_all()