Subversion Repositories pentevo

Rev

Rev 1054 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed | ?url?

  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.  
  30. class ZXPic:
  31.  
  32.         def __init__(self,filename):
  33.                
  34.                 with open(filename,'rb') as file:
  35.                         self.zxscr = bytes(file.read())
  36.  
  37.                 if( len(self.zxscr)!=6144 and len(self.zxscr)!=6912 ):
  38.                         sys.exit('Wrong zx file <{}> size, must be 6144 or 6912'.format(filename))
  39.  
  40.                 if( len(self.zxscr)==6912 ):
  41.                         self.colored = True
  42.                 else:
  43.                         self.colored = False
  44.  
  45.                 self.pixels = bytes(self.zxscr[:6144])
  46.  
  47.                 if( self.colored ):
  48.                         self.attrs = bytes(self.zxscr[6144:])
  49.                 else:
  50.                         self.attrs = None
  51.  
  52.                 self.sz_x = 256
  53.                 self.sz_y = 192
  54.  
  55.  
  56.         def get_pix(self,x,y):
  57.                
  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))
  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.  
  69. class CharSet:
  70.  
  71.         def __init__(self, num_els, first_idx, sz_x, sz_y):
  72.  
  73.                 num_els   = int(num_els)
  74.                 first_idx = int(first_idx)
  75.                 sz_x      = int(sz_x)
  76.                 sz_y      = int(sz_y)
  77.  
  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!'
  83.  
  84.                 self.num_els   = num_els
  85.                 self.first_idx = first_idx
  86.                 self.sz_x      = sz_x
  87.                 self.sz_y      = sz_y
  88.  
  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.  
  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!'
  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):
  179.                                 font.set_pix(char_idx, y, x, pic.get_pix(x_origin + x, y_origin + y))
  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.  
  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):             
  203.                        
  204.                                 # actual layout of the resulting font is defined here
  205.                                 offs = ((i>>3)*36 + x + y*6) & 0x3FF
  206.                                 #
  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.  
  216. def main():
  217.  
  218.         # parse arguments
  219.         p = argparse.ArgumentParser()
  220.         #
  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)')
  225.         #
  226.         args = p.parse_args()
  227.  
  228.  
  229.         pic = ZXPic(args.scr)
  230.  
  231.         font = generate_font(pic=pic,
  232.                              start_cx=0, start_cy=1,
  233.                              first_idx=32, num_els=224)
  234.  
  235.         binary = gen_binary(font)
  236.  
  237.  
  238.         bin_name = args.out + ".bin"
  239.  
  240.         with open(bin_name,"wb") as wrbin:
  241.                 wrbin.write(binary)
  242.  
  243.  
  244. if __name__=="__main__":
  245.         main()
  246.  
  247.