T720 is a library that provides functions to create a 2D software rendered 256 color retro terminal.
Ideal for simple text or rougelike games.
It creates a 720p (1280 x 720 pixel) surface with 80 x 45 chars (each 16 x 16 pixels),
the output can be rendered into a user supplied buffer.
I started this little project to get into cross-platform development.
Features:
- access to all buffers (palette / surface / font)
- palette color cycling like in the old days
- support for custom fonts (layout/format mapping)
- simple routines to create lines, blocked out areas or outlines
- support for sprites
- easy mapping of chars and tiles
- scrolling (up & down)
- translation of coordinates from pixel to terminal (tile) space
- effect filters (for monochrome palettes)
- the renderer supports 24 & 32 bit rgb/a and bgr/a buffers (which can be vertically/y flipped)
Last update:
Updated 23.03.2025 - Alpha 10:
- new function tKeyInput()
- fixed a bug in the image loading routines (Windows)
Download (t720_alpha_10): currently not available!
Have fun

Images:




Include (t720.pbi):
Code: Select all
EnableExplicit
;------------------------------------------------------------------------------------
; T720 (TERMINAL 720)
; A software rendered 256 color (palette based) retro terminal.
;------------------------------------------------------------------------------------
; Author: Mijikai
; Version: Alpha 10
; Platform: Linux / Windows
; Compiler: PureBasic 6.20 LTS (x64) - C Backend
;------------------------------------------------------------------------------------
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
Import "t720.so"
tInit.i(Version.i = #Null);<- needs to be called once at the beginning
tPaletteLoad.i(*Buffer,File.s);<- loads a bitmap (in memory or as file) and fills the palette with the color of the first 256 pixel
tPaletteCatch.i(*Buffer);<- loads a palette from memory (256 colors x 4 bytes)
tPaletteSet.i(Index.i,Color.i);<- sets the color of the palette at the specified index
tPaletteGet.i(Index.i,*Color);<- gets the color of the palette at the specified index
tPaletteLookup.i(Color.i);<- looks for a color in the palette and returns its index (or 257 if the color is not found)
tPaletteCycle.i(Index.i,Colors.i,Steps.i = 1);<- cycles the palette from the specified index (colors = range) x amount of steps left or right (steps can be negative!)
tPixelClear.i(Index.i = 0);<- clears all pixels (backbuffer) with the specified by palette index
tPixelSet.i(X.i,Y.i,Index.i = 127);<- sets a pixel palette index
tPixelGet.i(X.i,Y.i,*Index);<- gets a pixel palette index
tPixelDraw.i(X.i,Y.i,*Pixel,Span.i,SrcX.i,SrcY.i,Width.i,Height.i,Index.i = -1);<- draws pixels stored in memory
tPixelChange.i(Mode.i,IndexA.i,IndexB.i,*Pixel.Ascii,Span.i,SrcX.i,SrcY.i,Width.i,Height.i);<- changes pixel palette indexes in memory
tPixelFilter.i(Mode.i = 0,Random.i = #False);<- adds a filter effect with 3 modes (0 - 3) and 3 random options (0 - 2) good for monochrome CRT effects
tTileMap.i(Offset.i,*Pixel,CountX.i,CountY.i);<- maps pixels to tiles (the offset can be specified)
tTileSet.i(X.i,Y.i,*Link);<- sets a user defined value for a specific tile
tTileGet.i(X.i,Y.i,*Link);<-gets the user definded value from a tile
tTileDraw.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1);<- draws a tile (previously mapped with tTileMap())
tTileDrawRegion.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1);<- draws multiple tiles at once (added with one call to tTileMap())
tTileDrawLine.i(Id.i,X1.i,Y1.i,X2.i,Y2.i,*Link = 0,Index.i = -1);<- draws a line composed of tiles
tTileDrawBlock.i(Id.i,X.i = 0,Y.i = 0,CountX.i = 0,CountY.i = 0,*Link = 0,Index.i = -1);<- draws a rect (block) composed of lines
tTileDrawOutline.i(Rect.i,Corner.i,X.i,Y.i,CountX.i,CountY.i,*Link = 0,Index.i = -1);<- draws a outline composed of tiles (individual tiles indexes can be set with the RGBA() macro for Rect and Corner)
tTileReplace.i(Id.i,IndexA.i,IndexB.i,Count.i = 0);<- replaces palette indexes in a specific tile
tTileExclude.i(Id.i,IndexA.i,IndexB.i,Count.i = 0);<- replaces all palette indexes unless its the index specified by IndexB in a specific tile
tTileSwap.i(Id.i,IndexA.i,IndexB.i,Count.i = 0);<- swaps two palette indexes in a specific tile
tTileMark.i(X.i,Y.i,Index.i = 127);<- highlights a tile (pus a one pixel border around it) with the selected palette index
tTileErase.i(X.i,Y.i,Index.i = 0,*Link = 0);<- deletes a tile at the specified tile position
tTileScroll.i(Count.i = 1,Index.i = 0);<- scroll the backbuffer up or down a specified amounf (Count) and sets the palette index for the new created lines
tTextFont.i(*Buffer,Format.s);<- registers a font (16 x 16 chars @ 16 x 16 pixels) format describes what char is where using a string (ex. " ABCDEF")
tTextDraw.i(X.i,Y.i,Text.s,*Link = 0,Index.i = -1);<- draws text
tTextChar.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1);<- draws a single char
tTextReplace.i(IndexA.i,IndexB.i);<- same as tTileReplace() but for the font chars (tiles)
tTextExclude.i(IndexA.i,IndexB.i)
tTextSwap.i(IndexA.i,IndexB.i)
tSpriteLoad.i(*Buffer,File.s,UsePalette.i = #False);<- loads a bitmap (from file or memory) and uses the palette (if UsePalette is #True) or the green color channel to map the pixles to palette indexes
tSpriteDraw.i(*Sprite,X.i,Y.i,Center.i = #False,Index.i = -1);<- draws a sprite to the screen (not confined by the tile grid)
tSpriteDrawAngle.i(*Sprite,X.i,Y.i,Center.i = #False,Angle.i = 0,Index.i = -1);<- draws a angled (rotated sprite) using the three shrear method
tSpriteTest.i(*Sprite1,X1.i,Y1.i,Center1.i,*Sprite2,X2.i,Y2.i,Center2.i);<- test the colission between two (unrotated) sprites
tSpriteSave.i(*Sprite,File.s);<- saves the loaded sprite as binary file (palette indexes)
tSpriteFree.i(*Sprite);<- frees a sprite
tBufferInfo.i(*Info);<- returns a list of memory addresses for the vatious buffers (everything is accessible)
tBufferPush.i(Mode.i = 3);<- saves buffer states (0 = palette, 1 = surface, 2 = font, 3 = link, 4 = surface & link, 5 = all)
tBufferPop.i(Mode.i = 3);<- restores buffer states
tBufferDebug.i(Mode.i,Offset.i = 0,X.i = 0,Y.i = 0,CountX.i = 0,CountY.i = 0);<- debugs (draws the content of a specific buffer: 0 = palette, 1 = surface, 2 = font)
tTranslate.i(X.i,Y.i,*X,*Y,Width.i = 0,Height.i = 0);<- maps a position within a width and height to the tile index
tPointInRect.i(PosX.i,PosY.i,X.i,Y.i,Width.i,Height.i,Center.i = #False);<- checks if a point is inside a rect (useful for menus)
tScreenshot.i(File.s);<- saves the contents of the terminal backbuffer as screenshot (*.bmp)
tRender.i(*Buffer,Pitch.i,Bits.i,FormatBGR.i = #False,FlippedY.i = #False);<- rebders the backbuffer into a user specified memory location
tScreenOpen.i();<- creates a screen (fullscreen) - calls tInit()
tScreenWindowOpen.i();<- creates a maximized window with a screen - calls tInit()
tScreenPollEvents.i();<- polls window events (handles input)
tScreenRenderBegin.i();<- start rendering
tScreenRenderEnd.i();<- stop rendering
tScreenRenderFlip.i();<- flip buffers
tScreenExit.i();<- returns #True if [ESC] is pressed (tScreenPollEvents() needs to be called before)
tScreenClose.i();<- closes the screen
tKeyPressed.i(Code.i);<- checks if a key is pressed
tKeyPushed.i(Code.i);<- checks if a key has been pressed
tKeyInput.i();<- returns the char code of the last pressed key
tMousePosition.i(*X,*Y);<- gets the mouse position in pixels (Integer)
tMouseTile.i(*X,*Y);<- gets the mouse position in tile space (Integer)
tMouseLeft.i();<- is the left mouse button pressed
tMouseRight.i() ;<- is the right mouse button pressed
tMouseMiddle.i();<- is the middle mouse button pressed
tMouseLeftClick.i();<- was the left mouse button pressed
tMouseRightClick.i()
tMouseMiddleClick.i()
tMouseWheel.i();<- resturns the amout of the mouse wheel if turned (can be negative)
EndImport
CompilerElse
Import "t720.lib"
tInit.i(Version.i = #Null)
tPaletteLoad.i(*Buffer,File.s)
tPaletteCatch.i(*Buffer)
tPaletteSet.i(Index.i,Color.i)
tPaletteGet.i(Index.i,*Color)
tPaletteLookup.i(Color.i)
tPaletteCycle.i(Index.i,Colors.i,Steps.i = 1)
tPixelClear.i(Index.i = 0)
tPixelSet.i(X.i,Y.i,Index.i = 127)
tPixelGet.i(X.i,Y.i,*Index)
tPixelDraw.i(X.i,Y.i,*Pixel,Span.i,SrcX.i,SrcY.i,Width.i,Height.i,Index.i = -1)
tPixelChange.i(Mode.i,IndexA.i,IndexB.i,*Pixel.Ascii,Span.i,SrcX.i,SrcY.i,Width.i,Height.i);
tPixelFilter.i(Mode.i = 0,Random.i = #False)
tTileMap.i(Offset.i,*Pixel,CountX.i,CountY.i)
tTileSet.i(X.i,Y.i,*Link)
tTileGet.i(X.i,Y.i,*Link)
tTileDraw.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1)
tTileDrawRegion.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1)
tTileDrawLine.i(Id.i,X1.i,Y1.i,X2.i,Y2.i,*Link = 0,Index.i = -1)
tTileDrawBlock.i(Id.i,X.i = 0,Y.i = 0,CountX.i = 0,CountY.i = 0,*Link = 0,Index.i = -1)
tTileDrawOutline.i(Rect.i,Corner.i,X.i,Y.i,CountX.i,CountY.i,*Link = 0,Index.i = -1)
tTileReplace.i(Id.i,IndexA.i,IndexB.i,Count.i = 0)
tTileExclude.i(Id.i,IndexA.i,IndexB.i,Count.i = 0)
tTileSwap.i(Id.i,IndexA.i,IndexB.i,Count.i = 0)
tTileMark.i(X.i,Y.i,Index.i = 127)
tTileErase.i(X.i,Y.i,Index.i = 0,*Link = 0)
tTileScroll.i(Count.i = 1,Index.i = 0)
tTextFont.i(*Buffer,Format.s)
tTextDraw.i(X.i,Y.i,Text.s,*Link = 0,Index.i = -1)
tTextChar.i(Id.i,X.i,Y.i,*Link = 0,Index.i = -1)
tTextReplace.i(IndexA.i,IndexB.i)
tTextExclude.i(IndexA.i,IndexB.i)
tTextSwap.i(IndexA.i,IndexB.i)
tSpriteLoad.i(*Buffer,File.s,UsePalette.i = #False)
tSpriteDraw.i(*Sprite,X.i,Y.i,Center.i = #False,Index.i = -1)
tSpriteDrawAngle.i(*Sprite,X.i,Y.i,Center.i = #False,Angle.i = 0,Index.i = -1)
tSpriteTest.i(*Sprite1,X1.i,Y1.i,Center1.i,*Sprite2,X2.i,Y2.i,Center2.i)
tSpriteSave.i(*Sprite,File.s)
tSpriteFree.i(*Sprite)
tBufferInfo.i(*Info)
tBufferPush.i(Mode.i = 3)
tBufferPop.i(Mode.i = 3)
tBufferDebug.i(Mode.i,Offset.i = 0,X.i = 0,Y.i = 0,CountX.i = 0,CountY.i = 0)
tTranslate.i(X.i,Y.i,*X,*Y,Width.i = 0,Height.i = 0)
tPointInRect.i(PosX.i,PosY.i,X.i,Y.i,Width.i,Height.i,Center.i = #False)
tScreenshot.i(File.s)
tRender.i(*Buffer,Pitch.i,Bits.i,FormatBGR.i = #False,FlippedY.i = #False)
tScreenOpen.i()
tScreenWindowOpen.i()
tScreenPollEvents.i()
tScreenRenderBegin.i()
tScreenRenderEnd.i()
tScreenRenderFlip.i()
tScreenExit.i()
tScreenClose.i()
tKeyPressed.i(Code.i)
tKeyPushed.i(Code.i)
tKeyInput.i()
tMousePosition.i(*X,*Y)
tMouseTile.i(*X,*Y)
tMouseLeft.i()
tMouseRight.i()
tMouseMiddle.i()
tMouseLeftClick.i()
tMouseRightClick.i()
tMouseMiddleClick.i()
tMouseWheel.i()
EndImport
CompilerEndIf
;-------------------------------------------------------------------------------------
#T720_VERSION = $0000008
;-------------------------------------------------------------------------------------
Structure T720_INFO
*palette[2]
*surface[2]
*font[2]
EndStructure
Structure T720_SPRITE
width.l
height.l
pixels.i
*pixel
EndStructure
Enumeration
#KEY_0
#KEY_1
#KEY_2
#KEY_3
#KEY_4
#KEY_5
#KEY_6
#KEY_7
#KEY_8
#KEY_9
#KEY_A
#KEY_B
#KEY_C
#KEY_D
#KEY_E
#KEY_F
#KEY_G
#KEY_H
#KEY_I
#KEY_J
#KEY_K
#KEY_L
#KEY_M
#KEY_N
#KEY_O
#KEY_P
#KEY_Q
#KEY_R
#KEY_S
#KEY_T
#KEY_U
#KEY_V
#KEY_W
#KEY_X
#KEY_Y
#KEY_Z
#KEY_Escape
#KEY_Minus
#KEY_Equals
#KEY_Back
#KEY_Tab
#KEY_LeftBracket
#KEY_RightBracket
#KEY_Return
#KEY_LeftControl
#KEY_SemiColon
#KEY_Apostrophe
#KEY_Grave
#KEY_LeftShift
#KEY_BackSlash
#KEY_Comma
#KEY_Period
#KEY_Slash
#KEY_RightShift
#KEY_Multiply
#KEY_LeftAlt
#KEY_Space
#KEY_Capital
#KEY_F1
#KEY_F2
#KEY_F3
#KEY_F4
#KEY_F5
#KEY_F6
#KEY_F7
#KEY_F8
#KEY_F9
#KEY_F10
#KEY_F11
#KEY_F12
#KEY_NumLock
#KEY_Scroll
#KEY_Pad0
#KEY_Pad1
#KEY_Pad2
#KEY_Pad3
#KEY_Pad4
#KEY_Pad5
#KEY_Pad6
#KEY_Pad7
#KEY_Pad8
#KEY_Pad9
#KEY_Add
#KEY_Subtract
#KEY_Decimal
#KEY_PadEnter
#KEY_RightControl
#KEY_PadComma
#KEY_Divide
#KEY_RightAlt
#KEY_Pause
#KEY_Home
#KEY_Up
#KEY_Down
#KEY_Left
#KEY_Right
#KEY_End
#KEY_PageUp
#KEY_PageDown
#KEY_Insert
#KEY_Delete
EndEnumeration
;-------------------------------------------------------------------------------------
Code: Select all
EnableExplicit
XIncludeFile "t720.pbi"
Procedure.i Main()
Protected.i tx,ty
If tScreenOpen();<- calls tInit() and opens a screen (1280 x 720 @ 60 fps)
tTileMap(0,?block,1,3);<- map the tiles saved in the datasection
tTileReplace(0,1,80,2);<- replace some pixel color indexes (tile 1 @ index 0)
tTileReplace(2,1,120);<- "
Repeat
tScreenPollEvents();<- handle screen & input events
tMouseTile(@tx,@ty);<- get mouse position in tile space
If tScreenRenderBegin();<- start rendering to the screen backbuffer
tPixelClear();<- clear all pixels in the backbuffer
tTileDraw(0,4,2);<- draw the first tile @ index 0 (mapped by tTileMap())
tTileDraw(1,5,2);<- draw the next tile
tTileDraw(2,6,2);<- "
tTextDraw(32,2,"T720 - TERMINAL");<- draw some text
tTextDraw(32,3,"***************");<- "
tTextDraw(4,4,"Mouse:" + Str(tx) + "x" + Str(ty));<- "
tTileDrawOutline(RGBA(0,1,0,1),RGBA(2,2,2,2),30,1,19,4);<- draw a outline (border using tile indexes supplied via the RGBA() macro)
tTileMark(tx,ty);<- mark (highlight) the tile the mouse is in
tScreenRenderEnd();<- stop rendering to the screen backbuffer
EndIf
tScreenRenderFlip();<- flip screen buffers
Until tScreenExit();<- press [ESC] to exit
tScreenClose();<- close everything down
EndIf
ProcedureReturn #Null
EndProcedure
End Main()
DataSection
block:
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data.a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0;1
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;2
Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0
Data.a 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
Data.a 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
EndDataSection
Code: Select all
EnableExplicit
XIncludeFile "t720.pbi"
Procedure.i Main()
Protected.T720_INFO info
Protected.i i,x,Dim y(79)
If tScreenOpen()
tBufferInfo(@info);<- get infos about the different buffers
tTileMap(0,info\font[0],16,16);<- (tile) map the font buffer
Repeat
tScreenPollEvents()
If tScreenRenderBegin()
tPixelClear(70);<- clear the screen
For i = 0 To 79
For x = 0 To y(i)
tTileReplace(x + 100,0,40 + x * 2);<- change the color (palette index) of some tiles
tTileDraw(x + 100,i,x);<- draw a tile
tTileDrawLine(110,33,20,45,20);<- draw a (tile) line
tTileDraw(x + 1 + (y(i) ! 2),i,x,0,0)
tTileDraw(x + 100,i,44)
Next
y(i) + 1
If y(i) > 44
y(i) = -Random(40,0)
EndIf
Next
tPixelFilter(3,1);<- adds a pixel filter effect
tTextDraw(33,19,"TERMINAL-T720",0,-1);<- draws text
tTextDraw(33,20," M A T R I X ",0,0)
tScreenRenderEnd()
EndIf
tScreenRenderFlip()
Until tScreenExit()
tScreenClose()
EndIf
ProcedureReturn #Null
EndProcedure
End Main()