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 |