quickly blit a custom 4x6 font character to screen?

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

quickly blit a custom 4x6 font character to screen?

Post by tschak909 »

I have a custom 4x6 font character, that I am using to render the alphanumeric text for my PLATOTerm terminal emulator.

It's arranged as a linear set of bytes in an array:

Code: Select all

#ifdef __SPECTRUM__

unsigned char FONT_SIZE_X=4;
unsigned char FONT_SIZE_Y=6;

unsigned char font[]={
  0x00,0x00,0x00,0x00,0x00,0x00,           /* SPACE 0x20 */
  0x80,0x80,0x80,0x00,0x80,0x00,           /* ! 0x21     */
  0xA0,0xA0,0x00,0x00,0x00,0x00,           /* " 0x22     */
  0xA0,0xE0,0xA0,0xE0,0xA0,0x00,           /* # 0x23     */
  0x40,0xE0,0xC0,0x60,0xE0,0x40,           /* $ 0x24     */
  0xA0,0x20,0x40,0x80,0xA0,0x00,           /* % 0x25     */
  0x40,0xA0,0x40,0xA0,0x60,0x00,           /* & 0x26     */
  0x80,0x80,0x00,0x00,0x00,0x00,           /* ' 0x27     */
  0x20,0x40,0x40,0x40,0x20,0x00,           /* ( 0x28     */
...
};
Is there something I can use to quickly blit these to an arbitrary pixel position? Right now, I am literally brute force plotting each pixel with plot().

(it would need to also handle inverse, handling of non-set pixels for transparent vs completely replace, etc.)

-Thom
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

Post by tschak909 »

For reference, this is what I am currently implementing:

Feel free to cringe.

Code: Select all

/**
 * screen_char_draw(Coord, ch, count) - Output buffer from ch* of length count as PLATO characters
 */
void screen_char_draw(padPt* Coord, unsigned char* ch, unsigned char count)
{
  short offset; /* due to negative offsets */
  unsigned short x;      /* Current X and Y coordinates */
  unsigned short y;
  unsigned short* px;   /* Pointers to X and Y coordinates used for actual plotting */
  unsigned short* py;
  unsigned char i; /* current character counter */
  unsigned char a; /* current character byte */
  unsigned char j,k; /* loop counters */
  char b; /* current character row bit signed */
  unsigned char width=FONT_SIZE_X;
  unsigned char height=FONT_SIZE_Y;
  unsigned short deltaX=1;
  unsigned short deltaY=1;
  unsigned char mainColor=1;
  unsigned char altColor=0;
  unsigned char *p;
  unsigned char* curfont;
  unsigned char* aaddr;
  
  switch(CurMem)
    {
    case M0:
      curfont=font;
      offset=-32;
      break;
    case M1:
      curfont=font;
      offset=64;
      break;
    case M2:
      curfont=fontm23;
      offset=-32;
      break;
    case M3:
      curfont=fontm23;
      offset=32;      
      break;
    }

  if (CurMode==ModeRewrite)
    {
      altColor=0;
    }
  else if (CurMode==ModeInverse)
    {
      altColor=1;
    }
  
  if (CurMode==ModeErase || CurMode==ModeInverse)
    mainColor=0;
  else
    mainColor=1;

  x=scalex[(Coord->x)];
  y=scaley[(Coord->y)+15];
  
  if (FastText==padF)
    {
      goto chardraw_with_fries;
    }

  /* the diet chardraw routine - fast text output. */
  
  for (i=0;i<count;++i)
    {
      a=*ch;
      ++ch;
      a+=offset;
      p=&curfont[fontptr[a]];
      
      for (j=0;j<FONT_SIZE_Y;++j)
          {
            b=*p;
          
            for (k=0;k<FONT_SIZE_X;++k)
              {
                if (b<0) /* check sign bit. */
                {
#ifdef __SPECTRUM__
                  *zx_pxy2aaddr(x+1,y+1)=foregroundColor;
#endif
                  if (mainColor==0)
                    unplot(x,y);
                  else
                    plot(x,y);
                }

              ++x;
                b<<=1;
              }

          ++y;
          x-=width;
          ++p;
          }

      x+=width;
      y-=height;
    }

  return;

 chardraw_with_fries:
  if (Rotate)
    {
      deltaX=-abs(deltaX);
      width=-abs(width);
      px=&y;
      py=&x;
    }
    else
    {
      px=&x;
      py=&y;
    }
  
  if (ModeBold)
    {
      deltaX = deltaY = 2;
      width<<=1;
      height<<=1;
    }
  
  for (i=0;i<count;++i)
    {
      a=*ch;
      ++ch;
      a+=offset;
      p=&curfont[fontptr[a]];
      for (j=0;j<FONT_SIZE_Y;++j)
          {
            b=*p;

          if (Rotate)
            {
              px=&y;
              py=&x;
            }
          else
            {
              px=&x;
              py=&y;
            }

            for (k=0;k<FONT_SIZE_X;++k)
              {
                if (b<0) /* check sign bit. */
                {
                  if (ModeBold)
                    {
                      if (mainColor==0)
                        {
#ifdef __SPECTRUM__
                          *zx_pxy2aaddr(*px+1,*py)=foregroundColor;
                          *zx_pxy2aaddr(*px,*py+1)=foregroundColor;
                          *zx_pxy2aaddr(*px+1,*py+1)=foregroundColor;
#endif
                          unplot(*px+1,*py);
                          unplot(*px,*py+1);
                          unplot(*px+1,*py+1);
                        }
                      else
                        {
#ifdef __SPECTRUM__
                          *zx_pxy2aaddr(*px+1,*py)=foregroundColor;
                          *zx_pxy2aaddr(*px,*py+1)=foregroundColor;
                          *zx_pxy2aaddr(*px+1,*py+1)=foregroundColor;
#endif
                          plot(*px+1,*py);
                          plot(*px,*py+1);
                          plot(*px+1,*py+1);
                        }
                    }
#ifdef __SPECTRUM__
                  *zx_pxy2aaddr(*px,*py)=foregroundColor;
#endif
                  if (mainColor==0)
                    unplot(*px,*py);
                  else
                    plot(*px,*py);
                }
              else
                {
                  if (CurMode==ModeInverse || CurMode==ModeRewrite)
                    {
                      if (ModeBold)
                        {
                          if (altColor==0)
                            {
#ifdef __SPECTRUM__
                              *zx_pxy2aaddr(*px+1,*py)=foregroundColor;
                              *zx_pxy2aaddr(*px,*py+1)=foregroundColor;
                              *zx_pxy2aaddr(*px+1,*py+1)=foregroundColor;
#endif
                              unplot(*px+1,*py);
                              unplot(*px,*py+1);
                              unplot(*px+1,*py+1);
                            }
                          else
                            {
#ifdef __SPECTRUM__
                              *zx_pxy2aaddr(*px+1,*py)=foregroundColor;
                              *zx_pxy2aaddr(*px,*py+1)=foregroundColor;
                              *zx_pxy2aaddr(*px+1,*py+1)=foregroundColor;
#endif
                              plot(*px+1,*py);
                              plot(*px,*py+1);
                              plot(*px+1,*py+1);
                            }
                        }
#ifdef __SPECTRUM__
                      *zx_pxy2aaddr(*px,*py);
#endif
                      if (altColor==0)
                        unplot(*px,*py);
                      else
                        plot(*px,*py);      
                    }
                }

              x += deltaX;
                b<<=1;
              }

          y+=deltaY;
          x-=width;
          ++p;
          }

      Coord->x+=width;
      x+=width;
      y-=height;
    }

  return;
}
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Yeah that's not going to be the fastest solution in the world :)

I would consider fzx for this. It can print any size font (fixed width too) at pixel precision and optionally confined to a window. Its printing modes are OR (so mixed with the display), XOR and AND (pixels are cleared instead of plotted). Getting an inverse / bold etc would require specialized code and I'm not sure if it's easy to do well; a better way would be to define a separate bold or invert font. Fzx is intended to be cross-platform but realistically it's only available for the spectrum and timex hi-res screen so porting to other platforms would mean writing one putchar function for each one.

That's one possibility anyway but maybe not an exact match for your requirements.
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

For reference, fzx is in classic, compile with -pragma-redirect:fputc_cons=fputc_cons_fzx

There's a 4x6 font printer available for most of the machines you're targeting - it does exactly what you're doing (without bold, inverse, colour) in terms of calling plot, but it's written in machine code. You can switch it in with -pragma-redirect:fputc_cons=putc4x6

The final option on the ZX would be to compromise with 4x8 fonts. In which case you can use the standard screen printer (it has inverse, colour, but not bold). If you can live with this, then I could add support to the TMS9918 machines fairly easily.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Yes using the stdio drivers is another option but it looks like he's doing without stdio and going directly to screen. You can also call fzx directly instead of going through stdio.
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

Post by tschak909 »

The font _has to be 4x6 as I am literally rendering a font with a max density of 64 characters by 32 lines, and the text must be able to be placed at any pixel position on screen. Intractable.

-Thom
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

Post by tschak909 »

If you want to see what I've ported:
https://www.youtube.com/watch?v=_XlppdrIAN8

And if you have FUSE with Spectranet set up, you can do the following:

%mount 0,"irata.online"
%load""

:)
siggi
Well known member
Posts: 344
Joined: Thu Jul 26, 2007 9:06 am

Post by siggi »

tschak909 wrote:And if you have FUSE with Spectranet set up, you can do the following:

%mount 0,"irata.online"
%load""

:)
I will check that using my Zeddy and ZeddyNet ;)


Siggi
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

tschak909 wrote:The font _has to be 4x6 as I am literally rendering a font with a max density of 64 characters by 32 lines, and the text must be able to be placed at any pixel position on screen. Intractable.
Fair enough, the easiest path is probably to then look at the putc4x6 (it's in libsrc/graphics) and modify to meet your requirements.
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

Post by tschak909 »

is there an editor for FZX? or will I need to make new fonts by hand? (no biggie, just wondering)

-Thom
tschak909
Well known member
Posts: 171
Joined: Sun Sep 09, 2018 5:44 pm

Post by tschak909 »

Ok, so found FZXEditor...

I can't compromise with 4x8 fonts, because I need to output at most 32 lines per screen (instead of 24).. so FZX seems to be an option. MSX and the 9918 can be tackled once I get I/O routines for those platforms (This is one reason I wrote these unbelievably naive and portable routines, so I didn't have to fight on multiple fronts during a bring-up. With over two dozen platforms I am porting, I am having to choose my battles VERY carefully!)

How can I embed my FZX font and call FZX directly to render a character, given a pixel position? FWIW, I do receive data in string sized chunks, so if there's an optimization there, I can take advantage of it...)

-Thom
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

There are about 100 fzx fonts you can look at in the library:
https://github.com/z88dk/z88dk/tree/mas ... /fzx/fonts

The fzx routines are in classic but it looks like the header file was not included. That can be fixed fairly quickly.

This is the header file:
https://github.com/z88dk/z88dk/blob/mas ... /fzx.h#L35

Create a static fzx_state structure to hold the printing state.

Initialize it with a call to fzx_init()
https://github.com/z88dk/z88dk/blob/mas ... /fzx.h#L90
You're providing the font and the window in this call. The window is the "paper" area where text is rendered into and is measured in 16-bit pixel units. A struct rect_16 holds members x, width, y, height. This will also initialize the other members mainly to 0 but left margin to 3 (iirc to allow kerning) and sets the draw function to xor.

You may want to statically initialize the fzx_state structure instead of calling fzx_init. In particular with a fixed width font you may not want to have a left margin as you won't have any kerning. For filling in the structure, the provided draw functions are or, xor, clear ( https://github.com/z88dk/z88dk/blob/mas ... /fzx.h#L76 ). On the spectrum the colour members fgnd_attr and fgnd_mask determine output colour. It's an AND/OR operation.. first the existing colour on screen is ANDed with fgnd_mask and that is ORed with fgnd_attr to form the final colour. Set fgnd_attr=0 and fgnd_mask=0xff to keep the existing colour on screen; there is special code that detects this to skip the colouring.

As you can see the fzx_state structure contains the current x and y coordinate in pixels. These coords are relative to the "paper" defined as the window. You can change features like font, coords, etc, by directly changing members at any time.

To include your own font just import it via an asm file like this:

---

SECTION rodata_user
PUBLIC _myfont

_myfont:

BINARY "myfont.fzx"

---

And then add this asm file to your compile.

In your C program, add this to the header so that the C compiler can find the start address of the font:

extern struct fzx_font myfont;


The lowest level output is fzx_putc that puts out one char ( https://github.com/z88dk/z88dk/blob/mas ... fzx.h#L101 ). It's useful to look at the implementation to find out what is indicated in the return value - https://github.com/z88dk/z88dk/blob/mas ... tc.asm#L31 . You actually have to look at the c interface code for this one but what it does is return A (0 or 1) on error or a screen address on success. This function will adjust the x coord on success but it only returns an error if the char does not fit horizontally or vertically.

fzx_puts() will print a zero-terminated string and will move to the next line automatically. fzx_write() is similar but does a buffer with length. These generic routines are in https://github.com/z88dk/z88dk/tree/mas ... nt/fzx/z80 . There are also routines for word wrapping, measuring pixel width of strings and printing justified text.

If you also need to do typed input, you may want to use the stdio driver for both output and input.


For a very small font, your original approach might not be too bad if it is re-written to be quicker.


Anyway, as the fzx header is missing in classic at the moment, you can just copy the appropriate header out of the newlib and include it locally with #include "fzx.h".

sccz80: https://github.com/z88dk/z88dk/blob/mas ... font/fzx.h
zsdcc: https://github.com/z88dk/z88dk/blob/mas ... fzx.h#L101
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

There is a short example here showing use of the fzx structure. The link points at some rectangles used to define the fzx paper.
https://github.com/z88dk/z88dk/blob/mas ... odes.c#L25
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

This bit of code generates word-wrapped strings for output given a pixel width (output_terminal_width). It assumes the current x coord is at the left edge. It's written by Stefan Bylund and I don't think he will mind sharing this small snippet:

Code: Select all

static type8 *str_word_wrap(type8 *str)
{
    type8 *line;
    type8 *line_end;

    line = strtok(str, "\n");

    while (line != NULL)
    {
        line_end = fzx_string_partition_ww(out_term_font, line, out_term_line_width);
        *line_end = '\n';
        line = strtok(line_end + 1, "\n");
    }

    return str;
}
where:

out_term_line_width = paper.width - ioctl(fd, IOCTL_OTERM_FZX_LEFT_MARGIN, -1) - 1;

ie paper width - left margin - 1
Post Reply