I spent a few hours tweaking a nice little script to help me generate MAME bezel layouts for existing artwork more quickly using Python.

In case you don’t know what a MAME bezel is used for, take a look. Here’s our old friend Galaga, without any fancy effects or artwork:

Yeah “it’s Galaga” but it doesn’t really look like it did in the arcade. It’s sterile and boring. Here’s the same game, but with bezel artwork and some HLSL effects added:

Now that looks like the Galaga I remember!

Getting the HLSL effects in there is a whole different topic. But I have found hundreds, nay thousands, of these bezel artworks “around”. The problem is that most of them don’t come with MAME layout files, or they’re not correct.

The MAME .LAY File

The layout file is just a piece of XML that positions the emulated screen within the artwork in the correct location. It looks like this:

<mamelayout version="2">

<element name="bezel">

<image file="galaga_bezel.png"/>

</element>

<view name="Bezel Artwork">

<screen index="0">

<bounds x="970" y="736" width="2040" height="2720"/>

</screen>

<bezel element="bezel">

<bounds x="0" y="0" width="4000" height="3713"/>

</bezel>

</view>

</mamelayout>

You can see that if you have hundreds of games, figuring out these coordinates is going to be, well, not fun or fast.

How does this script work to save me time?

You will edit the script to point it at your directory full of multiple Bezel image files. When the script runs, it will pop up each image in turn, and let you draw a rectangle where the screen is located within the image. Then hit the space bar, or any other key. It will generate the Layout XML and move that, along with the bezel art, into a folder named for the game. Then all you do is drop it into the MAME/artwork folder and presto, you have Bezel. I went through 400 bezels while listening to some trippy ambient music and it was quite relaxing.

OK Chatty Cathy, Where’s the Damn Script?

You can find it in my Git repository, along with a README. If you improve this script, please consider sending me a pull request.

It’s a small enough script so I will show it to you here. It requires Python 3.6.1+ with Numpy 1.13.1 & OpenCV 3.3.1+contrib as dependencies.

# import the necessary packages

import os

import numpy as np

import cv2

import csv

import shutil dirName = "./arcade-bezel-overlays/"



MAX_DISPLAY_H = 1920

MAX_DISPLAY_V = 1080



xy1 = ()

xy2 = ()

bezelImage = np.array([])

gameName = ""

aspect = 1.0

mouseIsDown = False





def drawRect(img, xy_1, xy_2):

if xy_1 != () and xy_2 != ():

cv2.rectangle(img, xy_1, xy_2, (0, 255, 0), 2)





def makeTemplate(name, img, xy_1, xy_2):

width = img.shape[1]

height = img.shape[0]



template = f'''<!-- {name}.lay -->

<mamelayout version="2">

<element name="bezel">

<image file="{name}.png" />

</element>

<view name="Bezel Artwork">

<bezel element="bezel">

<bounds left="0" top="0" right="{width}" bottom="{height}" />

</bezel>

<screen index="0">

<bounds left="{xy_1[0]}" top="{xy_1[1]}" right="{xy_2[0]}" bottom="{xy_2[1]}" />

</screen>

</view>

</mamelayout>'''

return template





def parseAllGames():

games = {}

with open('resolutions.csv') as csvfile:

reader = csv.DictReader(csvfile, delimiter=";")

for row in reader:

games[str(row["game_name"])] = row

return games





def click_and_move(event, x, y, flags, param):

global xy1, xy2, mouseIsDown, gameName, bezelImage, aspect



if event == cv2.EVENT_LBUTTONDOWN:

xy1 = (x, y)

xy2 = ()

mouseIsDown = True



elif event == cv2.EVENT_LBUTTONUP:

xy2 = (x, y)

mouseIsDown = False



elif mouseIsDown and event == cv2.EVENT_MOUSEMOVE:

xa = int(xy1[0] + aspect * (y - xy1[1]))

# Hold down SHIFT to constrain to game aspect

xy2 = (xa, y) if flags & cv2.EVENT_FLAG_SHIFTKEY else (x, y)

# draw a rectangle around the region of interest

imgCopy = bezelImage.copy()

drawRect(imgCopy, xy1, xy2)

cv2.imshow(gameName, imgCopy)





def estimateRect(game, img):

global aspect



gw = float(game["video_x"])

gh = float(game["video_y"])

aspect = gw / gh



width = img.shape[1]

height = img.shape[0]



top = 0.025 * height

bottom = 0.975 * height



vspan = bottom - top

hspan = aspect * vspan



left = 0.5 * (width - hspan)

right = width - 0.5 * (width - hspan)



return (int(left), int(top)), (int(right), int(bottom))





def mainLoop():

global xy1, xy2, bezelImage, gameName



all_games = parseAllGames()



ld = os.listdir(dirName)

for filename in ld:

# print("opening " + dirName + filename)

gameName = str(os.path.splitext(filename)[0])

if gameName in all_games:

game = all_games[gameName]

bezelImage = cv2.imread(dirName + filename)

cv2.namedWindow(gameName, cv2.WINDOW_NORMAL)



width = min(MAX_DISPLAY_H, bezelImage.shape[1])

height = min(MAX_DISPLAY_V, bezelImage.shape[0])

cv2.resizeWindow(gameName, width, height)



xy1, xy2 = estimateRect(game, bezelImage)



imgCopy = bezelImage.copy()

drawRect(imgCopy, xy1, xy2)

cv2.imshow(gameName, imgCopy)



cv2.setMouseCallback(gameName, click_and_move)

cv2.waitKey(0)

template = makeTemplate(gameName, bezelImage, xy1, xy2)



os.makedirs(f'out/{gameName}', 0o777, True)

f = open(f'out/{gameName}/default.lay', 'w')

f.write(template)

f.close()

# shutil.copyfile(dirName + filename, f'out/{gameName}/{filename}')

shutil.move(dirName + filename, f'out/{gameName}/{filename}')

cv2.destroyAllWindows()

else:

print(f'WARNING: Could not locate {gameName} in list of all known MAME entries.')





mainLoop()

In Summary

I hope you find this useful, and please let me know what you think!