432 lines
11 KiB
Python
432 lines
11 KiB
Python
|
# 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()
|