Rev 1054 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1050 | lvd | 1 | #!/usr/bin/env python3 |
2 | |||
3 | """ |
||
4 | // ZX-Evo SDLoad Configuration (c) NedoPC 2023 |
||
5 | // |
||
6 | // font generator: takes 6912-positioned font and generates font in internal FPGA format |
||
7 | |||
8 | /* |
||
9 | This file is part of ZX-Evo Base Configuration firmware. |
||
10 | |||
11 | ZX-Evo Base Configuration firmware is free software: |
||
12 | you can redistribute it and/or modify it under the terms of |
||
13 | the GNU General Public License as published by |
||
14 | the Free Software Foundation, either version 3 of the License, or |
||
15 | (at your option) any later version. |
||
16 | |||
17 | ZX-Evo Base Configuration firmware is distributed in the hope that |
||
18 | it will be useful, but WITHOUT ANY WARRANTY; without even |
||
19 | the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||
20 | See the GNU General Public License for more details. |
||
21 | |||
22 | You should have received a copy of the GNU General Public License |
||
23 | along with ZX-Evo Base Configuration firmware. |
||
24 | If not, see <http://www.gnu.org/licenses/>. |
||
25 | */ |
||
26 | """ |
||
27 | |||
28 | import argparse,os,sys |
||
29 | |||
1051 | lvd | 30 | class ZXPic: |
1050 | lvd | 31 | |
1051 | lvd | 32 | def __init__(self,filename): |
33 | |||
34 | with open(filename,'rb') as file: |
||
35 | self.zxscr = bytes(file.read()) |
||
1050 | lvd | 36 | |
1054 | lvd | 37 | if( len(self.zxscr)!=6144 and len(self.zxscr)!=6912 ): |
1051 | lvd | 38 | sys.exit('Wrong zx file <{}> size, must be 6144 or 6912'.format(filename)) |
1050 | lvd | 39 | |
1051 | lvd | 40 | if( len(self.zxscr)==6912 ): |
41 | self.colored = True |
||
42 | else: |
||
43 | self.colored = False |
||
1050 | lvd | 44 | |
1051 | lvd | 45 | self.pixels = bytes(self.zxscr[:6144]) |
1050 | lvd | 46 | |
1051 | lvd | 47 | if( self.colored ): |
48 | self.attrs = bytes(self.zxscr[6144:]) |
||
49 | else: |
||
50 | self.attrs = None |
||
51 | |||
1052 | lvd | 52 | self.sz_x = 256 |
53 | self.sz_y = 192 |
||
1051 | lvd | 54 | |
1052 | lvd | 55 | |
56 | def get_pix(self,x,y): |
||
1051 | lvd | 57 | |
1052 | lvd | 58 | if( x<0 or x>=self.sz_x or y<0 or y>=self.sz_y ): |
59 | sys.exit('x,y must be within 0..{} and 0..{} range!'.format(self.sz_x-1,self.sz_y-1)) |
||
1051 | lvd | 60 | |
61 | |||
62 | bitnum = 7 - (x & 7) |
||
63 | |||
64 | offset = (x>>3) + (y & 7)*256 + ((y & 0x38)>>3)*32 + ((y & 0xC0)>>6)*2048 |
||
65 | |||
66 | return True if self.pixels[offset] & (1<<bitnum) else False |
||
67 | |||
68 | |||
1052 | lvd | 69 | class CharSet: |
1051 | lvd | 70 | |
1052 | lvd | 71 | def __init__(self, num_els, first_idx, sz_x, sz_y): |
1051 | lvd | 72 | |
1059 | lvd | 73 | num_els = int(num_els) |
74 | first_idx = int(first_idx) |
||
75 | sz_x = int(sz_x) |
||
76 | sz_y = int(sz_y) |
||
1051 | lvd | 77 | |
1059 | lvd | 78 | # check arguments |
79 | assert num_els>0, 'num_els must be positive!' |
||
80 | assert first_idx>=0, 'first_idx must be non-negative!' |
||
81 | assert sz_x>0, 'sz_x must be positive!' |
||
82 | assert sz_y>0, 'sz_y must be positive!' |
||
1051 | lvd | 83 | |
1059 | lvd | 84 | self.num_els = num_els |
85 | self.first_idx = first_idx |
||
86 | self.sz_x = sz_x |
||
87 | self.sz_y = sz_y |
||
1051 | lvd | 88 | |
1052 | lvd | 89 | # generate empty characters |
90 | self.charset = [None] * (self.first_idx + self.num_els) |
||
91 | |||
92 | for char_idx in range(self.first_idx, self.first_idx + self.num_els): |
||
93 | |||
94 | char = [None] * self.sz_y |
||
95 | |||
96 | for char_y in range(self.sz_y): |
||
97 | |||
98 | line = [False] * self.sz_x; |
||
99 | |||
100 | char[char_y] = line |
||
101 | |||
102 | self.charset[char_idx] = char |
||
103 | |||
104 | |||
105 | def set_pix(self, char_idx, char_y, char_x, value): |
||
106 | |||
107 | self.charset[char_idx][char_y][char_x] = value |
||
108 | |||
109 | |||
110 | def get_pix(self, char_idx, char_y, char_x): |
||
111 | |||
112 | return self.charset[char_idx][char_y][char_x] |
||
113 | |||
114 | |||
115 | |||
116 | def generate_font(pic, start_cx=0, start_cy=0, blk_sx=8, blk_sy=8, box_offx=0, box_offy=0, box_sx=6, box_sy=6, first_idx=32, num_els=224): |
||
117 | # pic -- byte pic with sizes pic.sz_x, pic.sz_y and get_pix(x,y) |
||
118 | # blk_sx/y -- size of font blocks (bounding boxes), typical 8x8 |
||
119 | # start_cx/cy -- coord of first element of font, in blocks (typical upper left, 0/0) |
||
120 | # box_offx/y -- offset of actual box with a letter inside block (typical 0/0) |
||
121 | # box_sx/y -- actual box size, 6/6 for 6x6 font etc. |
||
122 | # first_idx -- font index corresponding to start_cx/y position |
||
123 | # num_els -- how many font elements to parse |
||
124 | |||
125 | # check args |
||
126 | start_cx = int(start_cx) |
||
127 | start_cy = int(start_cy) |
||
128 | blk_sx = int(blk_sx) |
||
129 | blk_sy = int(blk_sy) |
||
130 | box_offx = int(box_offx) |
||
131 | box_offy = int(box_offy) |
||
132 | box_sx = int(box_sx) |
||
133 | box_sy = int(box_sy) |
||
134 | first_idx = int(first_idx) |
||
135 | num_els = int(num_els) |
||
136 | |||
137 | assert first_idx>=0 |
||
138 | assert num_els>0 |
||
139 | |||
140 | assert blk_sx>0 |
||
141 | assert blk_sy>0 |
||
142 | |||
143 | assert start_cx>=0 |
||
144 | assert start_cy>=0 |
||
145 | |||
146 | assert blk_sx*(start_cx+1) <= pic.sz_x |
||
147 | assert blk_sy*(start_cy+1) <= pic.sz_y |
||
148 | |||
149 | assert box_offx>=0 |
||
150 | assert box_offy>=0 |
||
151 | |||
152 | assert box_sx>0 |
||
153 | assert box_sy>0 |
||
154 | |||
1054 | lvd | 155 | assert box_offx+box_sx <= blk_sx, 'box_offx+box_sx > blk_sx!' |
156 | assert box_offy+box_sy <= blk_sy, 'box_offy+box_sy > blk_sy!' |
||
1052 | lvd | 157 | |
158 | |||
159 | # create empty font |
||
160 | font = CharSet(num_els, first_idx, box_sx, box_sy) |
||
161 | |||
162 | # load font data |
||
163 | curr_blk_x = start_cx |
||
164 | curr_blk_y = start_cy |
||
165 | |||
166 | pic_overflow = False |
||
167 | |||
168 | for char_idx in range(first_idx, first_idx + num_els): |
||
169 | |||
170 | assert not pic_overflow |
||
171 | |||
172 | # x/y of upper left part of the box |
||
173 | x_origin = curr_blk_x*blk_sx + box_offx |
||
174 | y_origin = curr_blk_y*blk_sy + box_offy |
||
175 | |||
176 | # copy pixels |
||
177 | for y in range(box_sy): |
||
178 | for x in range(box_sx): |
||
1054 | lvd | 179 | font.set_pix(char_idx, y, x, pic.get_pix(x_origin + x, y_origin + y)) |
1052 | lvd | 180 | |
181 | # step to next char in bitmap |
||
182 | curr_blk_x = curr_blk_x + 1 |
||
183 | if( curr_blk_x*blk_sx >= pic.sz_x ): |
||
184 | curr_blk_x = 0 |
||
185 | curr_blk_y = curr_blk_y + 1 |
||
186 | if( curr_blk_y*blk_sy >= pic.sz_y ): |
||
187 | curr_blk_y = 0 |
||
188 | pic_overflow = True |
||
189 | |||
190 | |||
191 | return font |
||
192 | |||
193 | |||
194 | |||
195 | |||
1054 | lvd | 196 | def gen_binary(font): |
197 | |||
198 | binary = bytearray(1024) #zeroed |
||
199 | |||
200 | for i in range(32,256): |
||
201 | for y in range(6): |
||
202 | for x in range(6): |
||
1059 | lvd | 203 | |
204 | # actual layout of the resulting font is defined here |
||
1054 | lvd | 205 | offs = ((i>>3)*36 + x + y*6) & 0x3FF |
1059 | lvd | 206 | # |
1054 | lvd | 207 | bit = 1<<(7-(i&7)) |
208 | |||
209 | if font.get_pix(i,y,x): |
||
210 | binary[offs] = binary[offs] | bit |
||
211 | |||
212 | return binary |
||
213 | |||
214 | |||
215 | |||
1050 | lvd | 216 | def main(): |
217 | |||
1051 | lvd | 218 | # parse arguments |
219 | p = argparse.ArgumentParser() |
||
220 | # |
||
1052 | lvd | 221 | p.add_argument('--scr', '-s', action='store', required=True, help='Filename of 6912 or 6144 ZX screen with font') |
222 | p.add_argument('--out', '-o', action='store', required=True, help='Filename prefix for resulting file(s). Extensions will be added as needed') |
||
223 | p.add_argument( '-x', type=int, action='store', default=0, help='Initial X position of 8x8 block with first symbol (that must be a space)') |
||
224 | p.add_argument( '-y', type=int, action='store', default=0, help='Initial Y position of 8x8 block with first symbol (that must be a space)') |
||
1051 | lvd | 225 | # |
226 | args = p.parse_args() |
||
1050 | lvd | 227 | |
1051 | lvd | 228 | |
1052 | lvd | 229 | pic = ZXPic(args.scr) |
230 | |||
1054 | lvd | 231 | font = generate_font(pic=pic, |
232 | start_cx=0, start_cy=1, |
||
233 | first_idx=32, num_els=224) |
||
1052 | lvd | 234 | |
1054 | lvd | 235 | binary = gen_binary(font) |
1052 | lvd | 236 | |
237 | |||
1054 | lvd | 238 | bin_name = args.out + ".bin" |
1052 | lvd | 239 | |
1054 | lvd | 240 | with open(bin_name,"wb") as wrbin: |
241 | wrbin.write(binary) |
||
242 | |||
243 | |||
1050 | lvd | 244 | if __name__=="__main__": |
245 | main() |
||
246 |