OSDN Git Service

Modified to handle Hengband's conversion requirements: allow use of a separate bmp...
[hengbandforosx/hengbandosx.git] / lib / xtra / graf / osx_bmp2png.py
1 """
2 Converts a tile set from bmp to png for use with the Mac OS X frontend.
3
4 Usage
5     python osx_bmp2png.py input_file output_file [options]
6
7 Options
8 --mask maskfile
9     Reads the mask (zero for transparent pixels; non-zero for opaque pixels)
10     from the bmp file, maskfile.  maskfile must have the same x and y
11     dimensions as input_file but need not match the color space or bit depth.
12     --mask overrides --tcoord, --terrain, and --transparent.
13
14 --tcoord hcoord vcoord
15     Reads the color to be treated as transparent for non-terrain tiles
16     from the position (hcoord, vcoord) in the tile set.  Both coordinates
17     are non-negative integers indexed from zero.  Is overridden by
18     --transparent or --mask if either of those options are set.  When not
19     set, the default is to read the color to be treated as transparent from
20     (0,0).
21
22 --terrain lx1 ly1 ux1 uy1 ...
23     Sets bounding boxes for parts of the tile set that are to be treated
24     as completely opaque (i.e. as background terrain).  Each bound box is
25     given by the coordinates for the lower corner, (lx,ly), and upper
26     corner, (ux,uy), with ux > lx and uy > ly.  If not set, the default
27     is to use seven bounding boxes, (0,0) to (256,16), (64,16) to
28     (256,24), (216,80) to (240,88), (48,272) to (152,280), (176,272) to
29     (192,280), (0,632) to (256,688), and (0,712) to (256,736) which are the
30     terrain tiles in Hengband's old 8x8 tile set.  Is overridden by the
31     --mask option if that option is set.
32
33 --transparent red green blue
34     Sets the color, as RGB values between 0 and 255, which will be treated
35     as completely transparent for non-terrain tiles.  If not set, uses
36     --tcoord or its default to get the color.  Is overridden by the --mask
37     option if that option is set.
38
39 Example
40 This is the conversion that was used to prepare the old tile set for the
41 OS X front end:
42     python osx_bmp2png.py 8x8.bmp 8x8.png
43 That is equivalent to
44     python osx_bmp2png.py 8x8.bmp 8x8.png -tcoord 0 0 \
45         -terrain 0 0 256 16 64 16 256 24 216 80 240 88 48 272 152 280 \
46         176 272 192 280 0 632 256 688 0 712 256 736
47
48 This is the conversion that was used to prepare Adam Bolt's tile set for the
49 OS X front end:
50     python osx_bmp2png.py 16x16.bmp 16x16.png --mask 16x16-mask.bmp
51
52 Requirements
53 argparse, PIL (Python imaging library) or equivalent, and
54 numpy (1.7 or later)
55
56 The conversions are required because CGImageSourceCreateWithURL() does not
57 handle the bmp format.
58 """
59
60 import argparse
61 import PIL.Image
62 import numpy
63
64 aparser = argparse.ArgumentParser(description='Converts bmp tile set to png.')
65 aparser.add_argument('input_file', type=str,
66         help='the path to the input tile set')
67 aparser.add_argument('output_file', type=str,
68         help='the path to the converted tile set')
69 aparser.add_argument('--mask', default='',
70         help='File name for BMP file with transparency mask')
71 aparser.add_argument('--tcoord', type=int, nargs=2, default=(0,32),
72         help='Position to be read for transparent color')
73 aparser.add_argument('--terrain', type=int, nargs='+',
74         default=(0,0,256,16,64,16,256,24,216,80,240,88,48,272,152,280,176,272,192,280,0,632,256,688,0,712,256,736),
75         help='Bounding box coordinates for terrain tiles')
76 aparser.add_argument('--transparent', type=int, nargs=3,
77         help='RGB value to be treated as transparent')
78
79 args = aparser.parse_args()
80
81 tin = PIL.Image.open(args.input_file)
82
83 if args.mask == '' and len(args.terrain) % 4 != 0:
84         raise IndexError('Need four values for each bounding box.')
85 for i in range(0, len(args.terrain), 4):
86         if (args.terrain[i] > args.terrain[i + 2] or
87                         args.terrain[i + 1] > args.terrain[i + 3]):
88                 raise RuntimeError('Lower bounding box coordinate exceeds the upper one')
89         if (args.terrain[i] < 0 or args.terrain[i + 2] > tin.width or
90                         args.terrain[i + 1] < 0 or
91                         args.terrain[i + 3] > tin.height):
92                 raise IndexError('Bounding box outside of image bounds')
93
94 palette = tin.getpalette()
95
96 if args.mask == '':
97         if args.transparent:
98                 trind = None
99                 trarr = numpy.array(args.transparent)
100         else:
101                 if (args.tcoord[0] < 0 or args.tcoord[0] >= tin.width or
102                                 args.tcoord[1] < 0 or
103                                 args.tcoord[1] >= tin.height):
104                         raise IndexError('Coordinates for transparent pixel are out of bounds')
105                 if palette:
106                         trind = tin.getpixel(tuple(args.tcoord))
107                 else:
108                         trarr = numpy.array(tin.getpixel(args.tcoord)[0:3])
109
110 arrin = numpy.array(tin)
111
112 # Copy over the RGB components.
113 arrout = numpy.empty((tin.height,tin.width,4), dtype='u1')
114 if palette:
115         parr = numpy.reshape(numpy.array(palette), (len(palette) // 3, 3))
116         arrout[:,:,0:3] = parr[arrin]
117 else:
118         arrout[:,:,0:3] = arrin[:,:,0:3]
119
120
121 # Set up the alpha channel.
122 if args.mask == '':
123         # Set up mask for portions of the data set not containing terrain.
124         arrt = numpy.ones((tin.height, tin.width), dtype=numpy.dtype(bool))
125         ind0, ind1 = numpy.meshgrid(numpy.linspace(0, tin.height - 1, tin.height),
126                 numpy.linspace(0, tin.width - 1, tin.width), indexing='ij')
127         for i in range(0, len(args.terrain), 4):
128                 arrt = numpy.logical_and(arrt,
129                         numpy.logical_or(numpy.logical_or(
130                         ind1 < args.terrain[i], ind0 < args.terrain[i + 1]),
131                         numpy.logical_or(ind1 >= args.terrain[i + 2],
132                         ind0 >= args.terrain[i + 3])))
133         del ind0, ind1
134
135         # Combine with where the input is equal to transparent color to get
136         # the transparency mask.
137         if palette:
138                 if trind is not None:
139                         arrout[:,:,3] = numpy.where(
140                                 numpy.logical_and(arrt, arrin == trind),
141                                 numpy.zeros((tin.height, tin.width), dtype='u1'),
142                                 255 * numpy.ones((tin.height, tin.width), dtype='u1'))
143                 else:
144                         arrout[:,:,3] = numpy.where(
145                                 numpy.logical_and(arrt, numpy.logical_and(
146                                 arrout[:,:,0] == trarr[0], numpy.logical_and(
147                                 arrout[:,:,1] == trarr[1], arrout[:,:,2] == trarr[2]))),
148                                 numpy.zeros((tin.height, tin.width), dtype='u1'),
149                                 255 * numpy.ones((tin.height, tin.width), dtype='u1'))
150         else:
151                 arrout[:,:,3] = numpy.where(
152                         numpy.logical_and(arrt, numpy.logical_and(
153                         arrin[:,:,0] == trarr[0], numpy.logical_and(
154                         arrin[:,:,1] == trarr[1], arrin[:,:,2] == trarr[2]))),
155                         numpy.zeros((tin.height, tin.width), dtype='u1'),
156                         255 * numpy.ones((tin.height, tin.width), dtype='u1'))
157         del arrt
158
159 else:
160         tmask = PIL.Image.open(args.mask)
161         if tmask.height != tin.height or tmask.width != tin.width:
162                 raise RuntimeError('Dimensions of mask do not match input dimensions')
163         arrmask = numpy.array(tmask)
164         if tmask.getpalette():
165                 arrout[:,:,3] = numpy.where(arrmask == 0,
166                         numpy.zeros((tin.height, tin.width), dtype='u1'),
167                         255 * numpy.ones((tin.height, tin.width), dtype='u1'))
168         else:
169                 arrout[:,:,3] = numpy.where(
170                         numpy.logical_and(arrmask[:,:,0] == 0,
171                         numpy.logical_and(arrmask[:,:,1] == 0,
172                         arrmask[:,:,2] == 0)),
173                         numpy.zeros((tin.height, tin.width), dtype='u1'),
174                         255 * numpy.ones((tin.height, tin.width), dtype='u1'))
175         del arrmask, tmask
176
177
178 tout = PIL.Image.fromarray(arrout)
179 tout.save(args.output_file)