Subversion Repositories pentevo

Rev

Rev 1053 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  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.                 # check arguments
  74.                 if( int(num_els)<=0 ):
  75.                         sys.exit('num_els must be positive!')
  76.                 else:
  77.                         self.num_els = int(num_els)
  78.  
  79.                 if( int(first_idx)<0 ):
  80.                         sys.exit('first_idx must be non-negative!')
  81.                 else:
  82.                         self.first_idx = int(first_idx)
  83.  
  84.                 if( int(sz_x)<1 ):
  85.                         sys.exit('sz_x must be positive!')
  86.                 else:
  87.                         self.sz_x = int(sz_x)
  88.  
  89.                 if( int(sz_y)<1 ):
  90.                         sys.exit('sz_y must be positive!')
  91.                 else:
  92.                         self.sz_y = int(sz_y)
  93.  
  94.                 # generate empty characters
  95.                 self.charset = [None] * (self.first_idx + self.num_els)
  96.  
  97.                 for char_idx in range(self.first_idx, self.first_idx + self.num_els):
  98.                        
  99.                         char = [None] * self.sz_y
  100.  
  101.                         for char_y in range(self.sz_y):
  102.  
  103.                                 line = [False] * self.sz_x;
  104.  
  105.                                 char[char_y] = line
  106.  
  107.                         self.charset[char_idx] = char
  108.  
  109.        
  110.         def set_pix(self, char_idx, char_y, char_x, value):
  111.  
  112.                 self.charset[char_idx][char_y][char_x] = value
  113.  
  114.  
  115.         def get_pix(self, char_idx, char_y, char_x):
  116.  
  117.                 return self.charset[char_idx][char_y][char_x]
  118.  
  119.  
  120.  
  121. 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):
  122. # pic -- byte pic with sizes pic.sz_x, pic.sz_y and get_pix(x,y)
  123. # blk_sx/y -- size of font blocks (bounding boxes), typical 8x8
  124. # start_cx/cy -- coord of first element of font, in blocks (typical upper left, 0/0)
  125. # box_offx/y -- offset of actual box with a letter inside block (typical 0/0)
  126. # box_sx/y -- actual box size, 6/6 for 6x6 font etc.
  127. # first_idx -- font index corresponding to start_cx/y position
  128. # num_els -- how many font elements to parse
  129.  
  130.         # check args
  131.         start_cx = int(start_cx)
  132.         start_cy = int(start_cy)
  133.         blk_sx = int(blk_sx)
  134.         blk_sy = int(blk_sy)
  135.         box_offx = int(box_offx)
  136.         box_offy = int(box_offy)
  137.         box_sx = int(box_sx)
  138.         box_sy = int(box_sy)
  139.         first_idx = int(first_idx)
  140.         num_els = int(num_els)
  141.  
  142.         assert first_idx>=0
  143.         assert num_els>0
  144.  
  145.         assert blk_sx>0
  146.         assert blk_sy>0
  147.  
  148.         assert start_cx>=0
  149.         assert start_cy>=0
  150.  
  151.         assert blk_sx*(start_cx+1) <= pic.sz_x
  152.         assert blk_sy*(start_cy+1) <= pic.sz_y
  153.  
  154.         assert box_offx>=0
  155.         assert box_offy>=0
  156.  
  157.         assert box_sx>0
  158.         assert box_sy>0
  159.  
  160.         assert box_offx+box_sx <= blk_sx, 'box_offx+box_sx > blk_sx!'
  161.         assert box_offy+box_sy <= blk_sy, 'box_offy+box_sy > blk_sy!'
  162.        
  163.  
  164.         # create empty font
  165.         font = CharSet(num_els, first_idx, box_sx, box_sy)
  166.  
  167.         # load font data
  168.         curr_blk_x = start_cx
  169.         curr_blk_y = start_cy
  170.  
  171.         pic_overflow = False
  172.  
  173.         for char_idx in range(first_idx, first_idx + num_els):
  174.                
  175.                 assert not pic_overflow
  176.  
  177.                 # x/y of upper left part of the box
  178.                 x_origin = curr_blk_x*blk_sx + box_offx
  179.                 y_origin = curr_blk_y*blk_sy + box_offy
  180.  
  181.                 # copy pixels
  182.                 for y in range(box_sy):
  183.                         for x in range(box_sx):
  184.                                 font.set_pix(char_idx, y, x, pic.get_pix(x_origin + x, y_origin + y))
  185.                
  186.                 # step to next char in bitmap
  187.                 curr_blk_x = curr_blk_x + 1
  188.                 if( curr_blk_x*blk_sx >= pic.sz_x ):
  189.                         curr_blk_x = 0
  190.                         curr_blk_y = curr_blk_y + 1
  191.                         if( curr_blk_y*blk_sy >= pic.sz_y ):
  192.                                 curr_blk_y = 0
  193.                                 pic_overflow = True
  194.  
  195.  
  196.         return font
  197.  
  198.  
  199.  
  200.  
  201. def gen_binary(font):
  202.        
  203.         binary = bytearray(1024) #zeroed
  204.  
  205.         for i in range(32,256):
  206.                 for y in range(6):
  207.                         for x in range(6):             
  208.                                
  209.                                 offs = ((i>>3)*36 + x + y*6) & 0x3FF
  210.  
  211.                                 bit = 1<<(7-(i&7))
  212.  
  213.                                 if font.get_pix(i,y,x):
  214.                                         binary[offs] = binary[offs] | bit
  215.  
  216.         return binary
  217.  
  218.  
  219.  
  220. def main():
  221.  
  222.         # parse arguments
  223.         p = argparse.ArgumentParser()
  224.         #
  225.         p.add_argument('--scr', '-s',           action='store', required=True, help='Filename of 6912 or 6144 ZX screen with font')
  226.         p.add_argument('--out', '-o',           action='store', required=True, help='Filename prefix for resulting file(s). Extensions will be added as needed')
  227.         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)')
  228.         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)')
  229.         #
  230.         args = p.parse_args()
  231.  
  232.  
  233.         pic = ZXPic(args.scr)
  234.  
  235.         font = generate_font(pic=pic,
  236.                              start_cx=0, start_cy=1,
  237.                              first_idx=32, num_els=224)
  238.  
  239.         binary = gen_binary(font)
  240.  
  241.  
  242.         bin_name = args.out + ".bin"
  243.  
  244.         with open(bin_name,"wb") as wrbin:
  245.                 wrbin.write(binary)
  246.  
  247.  
  248. if __name__=="__main__":
  249.         main()
  250.  
  251.