渲染填充的表面时,在BW显示器上更好地着色 [英] Better shading on BW display while rendering filled surfaces

查看:87
本文介绍了渲染填充的表面时,在BW显示器上更好地着色的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

序言

我终于解决了AT32UC3xxxxx和SSD1306 OLED I2C显示器之间的硬件不兼容问题(两者都有硬件错误,使它们不兼容),使我能够以400KBaud(每帧26.6ms)的价格使用硬件I2C.因此,我决定重写此LCD的旧驱动程序,以通过添加填充的表面(三角形,四边形)而不是仅添加线条和图案化的线条(我已经实现)来利用新的速度.

问题是显示尺寸为128x64像素,但仅打开/关闭BW时没有颜色或灰色阴影

因此,例如要渲染旋转的立方体,我需要以某种方式区分表面.我在考虑随机填充模式,其中表面填充到一定百分比而不是颜色.

这是我当前的代码(整个lib,但没有bug可以正常工作):

LCD_SSD1306_I2C.h

 //------------------------------------------------------------------------------------------//-SSD1306 I2C OLED LCD驱动程序版本2.000 ------------------------------------------------//------------------------------------------------------------------------------------------#ifndef _LCD_SSD1306_I2C_h#定义_LCD_SSD1306_I2C_h//------------------------------------------------------------------------------------------#定义SSD1306_SETCONTRAST 0x81#定义SSD1306_DISPLAYALLON_RESUME 0xA4#定义SSD1306_DISPLAYALLON 0xA5#定义SSD1306_NORMALDISPLAY 0xA6#定义SSD1306_INVERTDISPLAY 0xA7#定义SSD1306_DISPLAYOFF 0xAE#定义SSD1306_DISPLAYON 0xAF#定义SSD1306_SETDISPLAYOFFSET 0xD3#定义SSD1306_SETCOMPINS 0xDA#定义SSD1306_SETVCOMDETECT 0xDB#定义SSD1306_SETDISPLAYCLOCKDIV 0xD5#定义SSD1306_SETPRECHARGE 0xD9#定义SSD1306_SETMULTIPLEX 0xA8#定义SSD1306_SETLOWCOLUMN 0x00#定义SSD1306_SETHIGHCOLUMN 0x10#定义SSD1306_SETSTARTLINE 0x40#定义SSD1306_MEMORYMODE 0x20#定义SSD1306_COLUMNADDR 0x21#定义SSD1306_PAGEADDR 0x22#定义SSD1306_COMSCANINC 0xC0#定义SSD1306_COMSCANDEC 0xC8#定义SSD1306_SEGREMAP 0xA0#定义SSD1306_CHARGEPUMP 0x8D#定义SSD1306_SWITCHCAPVCC 0x2//滚动#defines#定义SSD1306_ACTIVATE_SCROLL 0x2F#定义SSD1306_DEACTIVATE_SCROLL 0x2E#定义SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3#定义SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26#定义SSD1306_LEFT_HORIZONTAL_SCROLL 0x27#定义SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29#定义SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A//------------------------------------------------------------------------------------------//#define I2C_send(adr,buf,siz){}//------------------------------------------------------------------------------------------#ifndef _brv8_tab#define _brv8_tab静态常量U8 brv8 [256] =//8位位反转{0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,​​76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,​​141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255};#万一//------------------------------------------------------------------------------------------class LCD_SSD1306_I2C//最多96行{民众://屏幕int adr;//I2C adrint xs,ys,sz;//解决U8 _scr [((128 * 96)> 3)+1];//屏幕缓冲区U8 * scr;U8 * pyx [96];//扫描线//图案U32 pat,pat_m,pat_b;//二进制模式,最大使用的位掩码,实际的位掩码//填充U32种子;int bufl [96];int bufr [96];//系统apivoid init(int _adr,int _xs,int _ys);//初始化LCD:I2C_adr,xs,ysvoid _command(U8 cmd);//*内部*请勿校准(通过I2C向LCD发送命令)无效rfsscr();//将实际的屏幕缓冲区复制到LCD(通过I2C)//gfx渲染col =< 0,1>无效的clrscr();//清除屏幕缓冲区void rotation(int ang);//旋转180度无效像素(int x,int y,bool col);//设置/解析像素布尔像素(int x,int y);//获取像素无效行(int x0,int y0,int x1,int y1,bool col);//线空三角形(int x0,int y0,int x1,int y1,int x2,int y2,bool col);//三角形void quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);void rect(int x0,int y0,int x1,int y1,bool col);//使用对角点的矩形//模式渲染无效pat_set(char * s);//从bianry数字字符串中设置二进制模式,MSB首先呈现无效pat_beg();//将模式状态设置为模式的开始void pat_line(int x0,int y0,int x1,int y1,bool col);void pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);void pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);void pat_rect(int x0,int y0,int x1,int y1,bool col);//填充的多边形col =< 0,255>void _fill_line(int x0,int y0,int x1,int y1);//*内部*不要调用它(将行渲染到bufl/bufr中)void _fill_seed();//*内部*不要调用它(重置种子)U8 _fill_rand();//*内部*不要调用它(获取伪随机数)void fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col);void fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col);//文字渲染void prnchr(int x,int y,char c);//在x,y处渲染char(y舍入为8的倍数)void prntxt(int x,int y,const char * txt);//在x,y处渲染文本(y舍入为8的倍数)};//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: _ command(U8 cmd){U8 buf [2] ={0x00,//0x40数据/命令cmd,};I2C_send(adr,buf,2);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: init(int _adr,int _xs,int _ys){int y;adr = _adr;xs = _xs;ys = _ys;sz = xs *(ys> 3);const bool _external_Vcc = false;//VRAM缓冲区scr = _scr + 1;//跳过第一个字节(VRAM/命令选择)对于(y = 0; y  3)* xs);//扫描线用于快速直接像素访问clrscr();//初始化序列U8 comPins = 0x02;U8对比度= 0x8F;if((xs == 128)&&(ys == 32)){comPins = 0x02;对比度= 0x8F;}否则if((xs == 128)&&(ys == 64)){comPins = 0x12;对比度=(_external_Vcc)?0x9F:0xCF;}否则if((xs == 96)&&(ys == 16)){comPins = 0x02;对比度=(_external_Vcc)?0x10:0xAF;}else {}//其他屏幕静态U8 init0 [27] ={0x00,//命令SSD1306_DISPLAYOFF,//0xAESSD1306_SETDISPLAYCLOCKDIV,0x80,//0xD5SSD1306_SETMULTIPLEX,ys-1,//0xA8SSD1306_SETDISPLAYOFFSET,0x00,//0xD3无偏移SSD1306_SETSTARTLINE |0x0,//第0行SSD1306_CHARGEPUMP,(_ external_Vcc)?0x10:0x14,//0x8DSSD1306_MEMORYMODE,0x00,//0x20水平(扫描线)SSD1306_SEGREMAP |0x1,SSD1306_COMSCANDEC,SSD1306_SETCOMPINS,comPins,SSD1306_SETCONTRAST,对比,SSD1306_SETPRECHARGE,(_ external_Vcc)?0x22:0xF1,//0xd9SSD1306_SETVCOMDETECT,0x40,//0xDBSSD1306_DISPLAYALLON_RESUME,//0xA4SSD1306_NORMALDISPLAY,//0xA6SSD1306_DEACTIVATE_SCROLL,SSD1306_DISPLAYON,//主屏幕打开};I2C_send(adr,init0,sizeof(init0));//初始化默认模式pat_set("111100100");//清除填充缓冲区对于(y = 0; y< 96; y ++){bufl [y] =-1;bufr [y] =-1;}}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: clrscr(){对于(int a = 0; a< sz; a ++)scr [a] = 0x00;}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: rotate(int ang){U8 a0,a1;整数x0,y0,x1,y1;如果(ang == 180)对于(y0 = 0,y1 = ys-8; y0< y1; y0 + = 8,y1- = 8)对于(x0 = 0,x1 = xs-1; x0< xs; x0 ++,x1--){a0 = brv8 [pyx [y0] [x0]];a1 = brv8 [pyx [y1] [x1]];pyx [y0] [x0] = a1;pyx [y1] [x1] = a0;}}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: rfsscr(){静态常量U8 init1 [] ={0x00,//命令SSD1306_MEMORYMODE,0,//水平加地址模式SSD1306_COLUMNADDR,0,xs-1,//列起始/结束地址(0/127复位)SSD1306_PAGEADDR,0,(ys> 3)-1,//页面开始/结束地址(0重置)};I2C_send(adr,(U8 *)init1,sizeof(init1));_scr [0] = 0x40;//0x40 VRAM//SW I2C可以在单个数据包中传递整个VRAM//I2C_send(adr,_scr,sz + 1);//HW I2C必须使用最多255个字节的数据包,因此128 + 1(因为UC3上的TWIM0具有8位计数器)int i,n = 128;U8 * p = _scr,a;对于(i = 0; i  = xs)||(y <0)||(y> = ys))返回false;//获取位return((pyx [y] [x]>(y& 7))& 1);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: line(int x0,int y0,int x1,int y1,bool col){int i,n,cx,cy,sx,sy;//行DDA参数x1- = x0;sx = 0;如果(x1> 0)sx = + 1;如果(x1< 0){sx = -1;x1 = -x1;}如果(x1)x1 ++;n = x1;y1- = y0;sy = 0;如果(y1> 0)sy = + 1;如果(y1< 0){sy = -1;y1 = -y1;}如果(y1)y1 ++;如果(n< y1)n = y1;//单个像素(不是一行)如果(!n){pixel(x0,y0,col);返回;}//ND DDA算法i是参数对于(cx = cy = n,i = 0; i  0)sx = + 1;如果(x1< 0){sx = -1;x1 = -x1;}如果(x1)x1 ++;n = x1;y1- = y0;sy = 0;如果(y1> 0)sy = + 1;如果(y1< 0){sy = -1;y1 = -y1;}如果(y1)y1 ++;如果(n< y1)n = y1;//单个像素(不是一行)如果(!n){pixel(x0,y0,col);返回;}//ND DDA算法i是参数对于(cx = cy = n,i = 0; i  0)sx = + 1;如果(x1< 0){sx = -1;x1 = -x1;}如果(x1)x1 ++;n = x1;y1- = y0;sy = 0;如果(y1> 0)sy = + 1;如果(y1< 0){sy = -1;y1 = -y1;}如果(y1)y1 ++;如果(n< y1)n = y1;//单个像素(不是一行)如果(!n){if((y0> = 0)&&(y0< ys)){bufl [y0] = x0;bufr [y0] = x0;}返回;}//目标缓冲区取决于y方向如果(sy> 0)buf = bufl;else buf = bufr;//ND DDA算法i是参数对于(cx = cy = n,i = 0; i  = xs)X1 = xs-1;如果(col == 0)for(x = X0; x <= X1; x ++)pixel(x,y,0);否则如果(col == 255)for(x = X0; x <= X1; x ++)pixel(x,y,1);否则(x = X0; x< = X1; x ++)pixel(x,y,(_ fill_rand()< = col));}}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col){行(x0,y0,x1,y1,col);行(x1,y1,x2,y2,col);行(x2,y2,x3,y3,col);行(x3,y3,x0,y0,col);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col){pat_line(x0,y0,x1,y1,col);pat_line(x1,y1,x2,y2,col);pat_line(x2,y2,x3,y3,col);pat_line(x3,y3,x0,y0,col);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col){int x,y,X0,X1,Y0,Y1;//要渲染的y范围Y0 = Y1 = y0;如果(Y0> y1)Y0 = y1;如果(Y1 <y1)Y1 = y1;如果(Y0> y2)Y0 = y2;如果(Y1 <y2)Y1 = y2;如果(Y0> y3)Y0 = y3;如果(Y1 <y3)Y1 = y3;//沿y轴裁剪到屏幕如果((Y1< 0)||((Y0> = ys))返回如果(Y0< 0)Y0 = 0;如果(Y1> = ys)Y1 = ys-1;//清除缓冲区对于(y = Y0; y <= Y1; y ++){bufl [y] = xs;bufr [y] =-1;}//渲染圆周_fill_line(x0,y0,x1,y1);_fill_line(x1,y1,x2,y2);_fill_line(x2,y2,x3,y3);_fill_line(x3,y3,x0,y0);//填充水平线_fill_seed();对于(y = Y0; y <= Y1; y ++){//x渲染范围X0 = bufl [y];X1 = bufr [y];如果(X0> X1){x = X0;X0 = X1;X1 = x;}//沿y轴裁剪到屏幕如果((X1< 0)|||(X0> = xs))继续;如果(X0< 0)X0 = 0;如果(X1> = xs)X1 = xs-1;如果(col == 0)for(x = X0; x <= X1; x ++)pixel(x,y,0);否则如果(col == 255)for(x = X0; x <= X1; x ++)pixel(x,y,1);否则(x = X0; x< = X1; x ++)pixel(x,y,(_ fill_rand()< = col));}}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: rect(int x0,int y0,int x1,int y1,bool col){行(x0,y0,x1,y0,col);行(x1,y0,x1,y1,col);行(x1,y1,x0,y1,col);行(x0,y1,x0,y0,col);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: pat_rect(int x0,int y0,int x1,int y1,bool col){pat_line(x0,y0,x1,y0,col);pat_line(x1,y0,x1,y1,col);pat_line(x1,y1,x0,y1,col);pat_line(x0,y1,x0,y0,col);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: prnchr(int x,int y,char c){y& = 0xFFFFFFF8;//8的倍数if((y< 0)|||(y> ys-8))返回;int i,a;a = c;a<< = 3;对于(i = 0; i< 8; i ++,x ++,a ++)如果((x> = 0)&&(x< xs))pyx [y] [x] = font [a];}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: prntxt(int x,int y,const char * txt){对于(; * txt; txt ++,x + = 8)prnchr(x,y,* txt);}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: pat_set(char * s){int i = 1;pat = 0;如果(s!= NULL)对于(i = 0;(* s)&&(i< 32); s ++,i ++){pat<< = 1;如果(* s =='1')pat | = 1;}如果(!i)i = 1;pat_m = 1<<(i-1);pat_beg();}//------------------------------------------------------------------------------------------无效LCD_SSD1306_I2C :: pat_beg(){pat_b = pat_m;}//------------------------------------------------------------------------------------------#undef I2C_send//------------------------------------------------------------------------------------------#万一//------------------------------------------------------------------------------------------ 

它是为AVR32 studio 2.7编写的,因此在没有U8/U16/U32的平台上使用unsigned int而不是相同(或更大)的位宽.

代码不是经过优化的,而是故意按原样编写的(不是为了提高速度,我在授课中也使用了此代码,以便学生可以掌握我在做什么)

现在,当我使用2D填充四边形渲染旋转立方体(在PC上的Win32 VCL测试应用程序上)时,

如您所见,由于分辨率低,立方体几乎无法区分,我希望有更好的东西.

最后我的问题:

如何在视觉上改善这种阴影

我正在考虑某种类似于Freescape引擎输出的阴影或预定义模式,如下所示:

您对如何进行这种2D凸多边形填充有任何想法或指示吗?

限制是低分辨率128x64 1bpp图像,低内存使用率,因为目标AVR32 UC3平台只有(16 + 32 + 32)KB RAM,并且万一有人要使用AVR8芯片,则只有2 KB(您知道Arduino使用那些.)

速度不是主要问题,因为目标平台具有约91 MIPS.

我对BW底纹不是很熟练(在过去,我主要使用线框来进行BW输出),因此即使是来自经验丰富的用户的提示,例如:

  • 多少个阴影 16/32/256 ?
  • 有多少大图案 4x4/8x8/16x16 ?
  • 如何生成模式(硬编码或某种算法)?
  • 如何处理多边形运动以避免噪声/闪烁(现在我在每个多边形上重置种子")

可能会对我有很大帮助.

这里有用于测试的示例输入图像:

解决方案

我设法使它正常工作.我最终得到了大小为8x8像素的硬编码17种阴影图案(在Paint中手动创建).这些是阴影:

从那里,我只使用渲染像素 mod 8 x,y 作为阴影LUT中的坐标.结果是:

如您所见,它比基于PRNG的阴影要好得多.这里是更新的代码:

 //------------------------------------------------------------------------------------------//-SSD1306 I2C OLED LCD驱动程序版本2.001 ------------------------------------------------//------------------------------------------------------------------------------------------/*[笔记]+ LCD端的I2C传输大小不受限制-没有内存地址重置命令,因此在VRAM传输过程中发生的任何损坏都会永久损坏输出,直到打开/关闭电源,但在一段时间超时或溢出后,地址会以某种方式重置-UC3硬件I2C限制每个数据包最多255个字节-ACK之前SDA上的UC3毛刺使LCD读取而不是写入操作并且不进行ACK*/#ifndef _LCD_SSD1306_I2C_h#定义_LCD_SSD1306_I2C_h//------------------------------------------------------------------------------------------#定义SSD1306_SETCONTRAST 0x81#定义SSD1306_DISPLAYALLON_RESUME 0xA4#定义SSD1306_DISPLAYALLON 0xA5#定义SSD1306_NORMALDISPLAY 0xA6#定义SSD1306_INVERTDISPLAY 0xA7#定义SSD1306_DISPLAYOFF 0xAE#定义SSD1306_DISPLAYON 0xAF#定义SSD1306_SETDISPLAYOFFSET 0xD3#定义SSD1306_SETCOMPINS 0xDA#定义SSD1306_SETVCOMDETECT 0xDB#定义SSD1306_SETDISPLAYCLOCKDIV 0xD5#定义SSD1306_SETPRECHARGE 0xD9#定义SSD1306_SETMULTIPLEX 0xA8#定义SSD1306_SETLOWCOLUMN 0x00#定义SSD1306_SETHIGHCOLUMN 0x10#定义SSD1306_SETSTARTLINE 0x40#定义SSD1306_MEMORYMODE 0x20#定义SSD1306_COLUMNADDR 0x21#定义SSD1306_PAGEADDR 0x22#定义SSD1306_COMSCANINC 0xC0#定义SSD1306_COMSCANDEC 0xC8#定义SSD1306_SEGREMAP 0xA0#定义SSD1306_CHARGEPUMP 0x8D#定义SSD1306_SWITCHCAPVCC 0x2//滚动#defines#定义SSD1306_ACTIVATE_SCROLL 0x2F#定义SSD1306_DEACTIVATE_SCROLL 0x2E#定义SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3#定义SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26#定义SSD1306_LEFT_HORIZONTAL_SCROLL 0x27#定义SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29#定义SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A//------------------------------------------------------------------------------------------//#define I2C_send(adr,buf,siz){}//------------------------------------------------------------------------------------------#ifndef _brv8_tab#define _brv8_tab静态常量U8 brv8 [256] =//8位位反转{0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,​​76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,​​141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255};#万一静态const U8 shade8x8 [17 * 8] =//17种阴影图案8x8像素{0、0、0、0、0、0、0、0,17,0,0,0,17,0,0,0,17,0,68,0,17,0,68,0,68,17,68,0,68,17,68,0,85,0,170,0,85,0,170,0,68,170,0,170,68,170,0,170,68,170,17,170,68,170,17,170,85,136,85,170,85,136,85,170,85,170、85,170、85,170、85,170,85,238、85,170,119,170、85,170,221,170,119,170,221,170,119,170,221,170,255,170,221,170,255,170,85,255,170,255,85,255,170,255,221,119,221,255,221,119,221,255,119,255,221,255,119,255,221,255,119,255,255,255,119,255,255,255,255,255,255,255,255,255,255,255};//------------------------------------------------------------------------------------------class LCD_SSD1306_I2C//最多96行{民众://屏幕int adr;//I2C adrint xs,ys,sz;//解决U8 _scr [((128 * 96)> 3)+1];//屏幕缓冲区U8 * scr;U8 * pyx [96];//扫描线//图案U32 pat,pat_m,pat_b;//二进制模式,最大使用的位掩码,实际的位掩码//填充int bufl [96];int bufr [96];//系统apivoid init(int _adr,int _xs,int _ys);//初始化LCD:I2C_adr,xs,ysvoid _command(U8 cmd);//*内部*请勿校准(通过I2C向LCD发送命令)无效rfsscr();//将实际的屏幕缓冲区复制到LCD(通过I2C)//gfx渲染col =< 0,1>无效的clrscr();//清除屏幕缓冲区无效的rotate(int ang);//旋转180度无效像素(int x,int y,bool col);//设置/解析像素布尔像素(int x,int y);//获取像素无效行(int x0,int y0,int x1,int y1,bool col);//线空三角形(int x0,int y0,int x1,int y1,int x2,int y2,bool col);//三角形void quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);void rect(int x0,int y0,int x1,int y1,bool col);//使用对角点的矩形//模式渲染无效pat_set(char * s);//从bianry数字字符串中设置二进制模式,MSB首先呈现无效pat_beg();//将模式状态设置为模式的开始void pat_line(int x0,int y0,int x1,int y1,bool col);void pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);void pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);void pat_rect(int x0,int y0,int x1,int y1,bool col);//填充的多边形col =< 0,255>void _fill_line(int x0,int y0,int x1,int y1);//*内部*不要调用它(将行渲染到bufl/bufr中)无效_fill(int Y0,int Y1,U8 col);//*内部*不要调用它(将bufl/bufr渲染到屏幕上)void fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col);void fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col);//text renderingvoid prnchr(int x,int y,char c);                       //render char at x,y (y is rounded to multiple of 8)void prntxt(int x,int y,const char *txt);              //render text at x,y (y is rounded to multiple of 8)};//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::_command(U8 cmd){U8 buf[2]={0x00,      //0x40 data/commandcmd,};I2C_send(adr,buf,2);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::init(int _adr,int _xs,int _ys){int y;adr=_adr;xs=_xs;ys=_ys;sz=xs*(ys>>3);const bool _external_Vcc=false;//VRAM bufferscr=_scr+1;                                        //skip first Byte (VRAM/command selection)for (y=0;y<ys;y++) pyx[y]=scr+((y>>3)*xs);         //scanlines for fast direct pixel accessclrscr();//Init sequenceU8 comPins = 0x02;U8 contrast = 0x8F;if((xs == 128) && (ys == 32)) { comPins = 0x02;contrast = 0x8F;}else if((xs == 128) && (ys == 64)) { comPins = 0x12;contrast = (_external_Vcc) ?0x9F : 0xCF;}else if((xs ==  96) && (ys == 16)) { comPins = 0x02;contrast = (_external_Vcc) ?0x10 : 0xAF;}else {}//Other screensstatic U8 init0[27]={0x00,                                          //commandsSSD1306_DISPLAYOFF,                            //0xAESSD1306_SETDISPLAYCLOCKDIV,0x80,               //0xD5SSD1306_SETMULTIPLEX,ys-1,                     //0xA8SSD1306_SETDISPLAYOFFSET,0x00,                 //0xD3 no offsetSSD1306_SETSTARTLINE | 0x0,                    //line 0SSD1306_CHARGEPUMP,(_external_Vcc)?0x10:0x14,  //0x8DSSD1306_MEMORYMODE,0x00,                       //0x20 horizontal (scanlines)SSD1306_SEGREMAP | 0x1,SSD1306_COMSCANDEC,SSD1306_SETCOMPINS,comPins,SSD1306_SETCONTRAST,contrast,SSD1306_SETPRECHARGE,(_external_Vcc)?0x22:0xF1,//0xd9SSD1306_SETVCOMDETECT,0x40,                    //0xDBSSD1306_DISPLAYALLON_RESUME,                   //0xA4SSD1306_NORMALDISPLAY,                         //0xA6SSD1306_DEACTIVATE_SCROLL,SSD1306_DISPLAYON,                             //Main screen turn on};I2C_send(adr,init0,sizeof(init0));//init default patternpat_set("111100100");//clear filling buffersfor (y=0;y<96;y++){bufl[y]=-1;bufr[y]=-1;}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::clrscr(){for (int a=0;a<sz;a++) scr[a]=0x00;}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::rotate(int ang){U8 a0,a1;int x0,y0,x1,y1;if (ang==180)for (y0=0,y1=ys-8;y0<y1;y0+=8,y1-=8)for (x0=0,x1=xs-1;x0<xs;x0++,x1--){a0=brv8[pyx[y0][x0]];a1=brv8[pyx[y1][x1]];pyx[y0][x0]=a1;pyx[y1][x1]=a0;}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::rfsscr(){static const U8 init1[] ={0x00,                          //commandsSSD1306_MEMORYMODE,0,          //horizontal addresing modeSSD1306_COLUMNADDR,0,xs-1,     //Column start/end address (0/127 reset)SSD1306_PAGEADDR,0,(ys>>3)-1,  //Page start/end address (0 reset)};I2C_send(adr,(U8*)init1,sizeof(init1));_scr[0]=0x40;                      //0x40 VRAM//SW I2C can pass whole VRAM in single packet// I2C_send(adr,_scr,sz+1);//HW I2C must use packets up to 255 bytes so 128+1 (as TWIM0 on UC3 has 8 bit counter)int i,n=128; U8 *p=_scr,a;for (i=0;i<sz;i+=n){ a=p[0];p[0]=0x40;I2C_send(adr,p,n+1);p[0]=a;p+=n;}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pixel(int x, int y,bool col){//clip to screenif ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;//add or remove bitif (col) pyx[y][x] |= (1<<(y&7));else     pyx[y][x] &= (255)^(1<<(y&7));}//------------------------------------------------------------------------------------------bool LCD_SSD1306_I2C::pixel(int x, int y){//clip to screenif ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return false;//get bitreturn ((pyx[y][x]>>(y&7))&1);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::line(int x0,int y0,int x1,int y1,bool col){int i,n,cx,cy,sx,sy;//line DDA parametersx1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;//single pixel (not a line)if (!n){ pixel(x0,y0,col);返回;}//ND DDA algo i is parameterfor (cx=cy=n,i=0;i<n;i++){pixel(x0,y0,col);cx-=x1;如果(cx< = 0){cx + = n;x0+=sx;}cy-=y1;如果(cy< = 0){cy + = n;y0+=sy;}}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_line(int x0,int y0,int x1,int y1,bool col){bool ccc;int i,n,cx,cy,sx,sy;//line DDA parametersx1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;//single pixel (not a line)if (!n){ pixel(x0,y0,col);返回;}//ND DDA algo i is parameterfor (cx=cy=n,i=0;i<n;i++){ccc=(pat&pat_b); ccc^=(!col);pat_b>>=1; if (!pat_b) pat_b=pat_m;pixel(x0,y0,ccc);cx-=x1;如果(cx< = 0){cx + = n;x0+=sx;}cy-=y1;如果(cy< = 0){cy + = n;y0+=sy;}}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::_fill_line(int x0,int y0,int x1,int y1){int i,n,cx,cy,sx,sy,*buf;//line DDA parametersx1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;//single pixel (not a line)if (!n){if ((y0>=0)&&(y0<ys)){bufl[y0]=x0;bufr[y0]=x0;}返回;}//target buffer depend on y directionif (sy>0) buf=bufl; else buf=bufr;//ND DDA algo i is parameterfor (cx=cy=n,i=0;i<n;i++){if ((y0>=0)&&(y0<ys)) buf[y0]=x0;cx-=x1;如果(cx< = 0){cx + = n;x0+=sx;}cy-=y1;如果(cy< = 0){cy + = n;y0+=sy;}}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::_fill(int Y0,int Y1,U8 col){U8 shd;int x,y,X0,X1,i;//select shade patterni=col;if (i< 0) i=0;if (i>17) i=17;i<<=3;//fill horizontal linesfor (y=Y0;y<=Y1;y++){shd=shade8x8[i+(y&7)];//x range to renderX0=bufl[y];X1=bufr[y];if (X0>X1){ x=X0;X0=X1;X1=x;}//clip to screen in y axisif ((X1<0)||(X0>=xs)) continue;if (X0<  0) X0=   0;if (X1>=xs) X1=xs-1;if (col==  0) for (x=X0;x<=X1;x++) pixel(x,y,0);else if (col==255) for (x=X0;x<=X1;x++) pixel(x,y,1);else for (x=X0;x<=X1;x++) pixel(x,y,shd&(1<<(x&7)));}}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col){line(x0,y0,x1,y1,col);line(x1,y1,x2,y2,col);line(x2,y2,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col){pat_line(x0,y0,x1,y1,col);pat_line(x1,y1,x2,y2,col);pat_line(x2,y2,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col){int y,Y0,Y1;//y range to renderY0=Y1=y0;if (Y0>y1) Y0=y1;if (Y1<y1) Y1=y1;if (Y0>y2) Y0=y2;if (Y1<y2) Y1=y2;//clip to screen in y axisif ((Y1<0)||(Y0>=ys)) return;if (Y0<  0) Y0=   0;if (Y1>=ys) Y1=ys-1;//清除缓冲区for (y=Y0;y<=Y1;y++){bufl[y]=xs;bufr[y]=-1;}//render circumference_fill_line(x0,y0,x1,y1);_fill_line(x1,y1,x2,y2);_fill_line(x2,y2,x0,y0);//fill horizontal lines_fill(Y0,Y1,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col){line(x0,y0,x1,y1,col);line(x1,y1,x2,y2,col);line(x2,y2,x3,y3,col);line(x3,y3,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col){pat_line(x0,y0,x1,y1,col);pat_line(x1,y1,x2,y2,col);pat_line(x2,y2,x3,y3,col);pat_line(x3,y3,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col){int y,Y0,Y1;//y range to renderY0=Y1=y0;if (Y0>y1) Y0=y1;if (Y1<y1) Y1=y1;if (Y0>y2) Y0=y2;if (Y1<y2) Y1=y2;if (Y0>y3) Y0=y3;if (Y1<y3) Y1=y3;//clip to screen in y axisif ((Y1<0)||(Y0>=ys)) return;if (Y0<  0) Y0=   0;if (Y1>=ys) Y1=ys-1;//清除缓冲区for (y=Y0;y<=Y1;y++){bufl[y]=xs;bufr[y]=-1;}//render circumference_fill_line(x0,y0,x1,y1);_fill_line(x1,y1,x2,y2);_fill_line(x2,y2,x3,y3);_fill_line(x3,y3,x0,y0);//fill horizontal lines_fill(Y0,Y1,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::rect(int x0,int y0,int x1,int y1,bool col){line(x0,y0,x1,y0,col);line(x1,y0,x1,y1,col);line(x1,y1,x0,y1,col);line(x0,y1,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_rect(int x0,int y0,int x1,int y1,bool col){pat_line(x0,y0,x1,y0,col);pat_line(x1,y0,x1,y1,col);pat_line(x1,y1,x0,y1,col);pat_line(x0,y1,x0,y0,col);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::prnchr(int x,int y,char c){y&=0xFFFFFFF8; //multiple of 8if ((y<0)||(y>ys-8)) return;int i,a;a=c; a<<=3;for (i=0;i<8;i++,x++,a++)if ((x>=0)&&(x<xs))pyx[y][x]=font[a];}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::prntxt(int x,int y,const char *txt){for (;*txt;txt++,x+=8) prnchr(x,y,*txt);}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_set(char *s){int i=1;pat=0;if (s!=NULL)for (i=0;(*s)&&(i<32);s++,i++){pat<<=1;if (*s=='1') pat|=1;}if (!i) i=1;pat_m=1<<(i-1);pat_beg();}//------------------------------------------------------------------------------------------void LCD_SSD1306_I2C::pat_beg(){pat_b=pat_m;}//------------------------------------------------------------------------------------------#undef I2C_send//------------------------------------------------------------------------------------------#万一//------------------------------------------------------------------------------------------ 

I moved all the filling into member function _fill just see how the LUT shade8x8 is used ...

[Edit1] Floyd–Steinberg dithering

Thanks to Scheff I wanted to try this kind of dithering. Sadly his code is not usable for polygon rasterization (due to its specifics and lack of input image) so I implemented in on my own. I struggled a while to make it work properly and the only way ot worked as expected was when:

  • color threshold is 50% of max intensity
  • error propagation is done on signed integers

The second requirement brings huge performance hit as simple bitshift cant be used anymore however I think hardcoded LUTs will overcome this.

Here my current implementation with booth ditherings:

void LCD_SSD1306_I2C::_fill(int Y0,int Y1,U8 col){int x,y,X0,X1,i;const U8 colmax=17;//bayer like dithering using precomputed shader patternsif (dither_mode==0){U8 shd;//select shade patterni=col;if (i< 0) i=0;if (i>17) i=17;i<<=3;//fill horizontal linesfor (y=Y0;y<=Y1;y++){shd=shade8x8[i+(y&7)];//x range to renderX0=bufl[y];X1=bufr[y];if (X0>X1){ x=X0;X0=X1;X1=x;}//clip to screen in y axisif ((X1<0)||(X0>=xs)) continue;if (X0<  0) X0=   0;if (X1>=xs) X1=xs-1;if (col==     0) for (x=X0;x<=X1;x++) pixel(x,y,0);else if (col==colmax) for (x=X0;x<=X1;x++) pixel(x,y,1);else for (x=X0;x<=X1;x++) pixel(x,y,shd&(1<<(x&7)));}}//Floyd–Steinberg ditheringif (dither_mode==1){int c0,c1,c2,rows[256+4],*r0=rows+1,*r1=rows+128+3,*rr,thr=colmax/2;//clear error;c0=0; for (x=0;x<256+4;x++) rows[x]=0;//fill horizontal linesfor (y=Y0;y<=Y1;y++){//x range to renderX0=bufl[y];X1=bufr[y];if (X0>X1){ x=X0;X0=X1;X1=x;}//clip to screen in y axisif ((X1<0)||(X0>=xs)) continue;if (X0<  0) X0=   0;if (X1>=xs) X1=xs-1;if (col==     0) for (x=X0;x<=X1;x++) pixel(x,y,0);else if (col==colmax) for (x=X0;x<=X1;x++) pixel(x,y,1);else for (x=X0;x<=X1;x++){c0=col;c0+=r0[x];;r0[x]=0;if (c0>thr){ pixel(x,y,1);c0-=colmax;}else        pixel(x,y,0);c2=c0;c1=(3*c0)/16; r1[x-1] =c1; c2-=c1;c1=(5*c0)/16; r1[x  ] =c1; c2-=c1;c1=(  c0)/16; r1[x+1] =c1; c2-=c1;r0[x+1]+=c2;}rr=r0; r0=r1; r1=rr;}}} 

The dither_mode is just int deciding which dithering to use. Here preview for both side by side. On the left is Bayer ditheringlike (with mine painted pattern) and right is the Floyd–Steinberg dithering:

The Floyd–Steinberg dithering sometimes create ugly (parallel lines) pattern in some rotations while the constant patterns look smooth in all cases So I will stick with my original rendering.

Prologue

I finally got around HW incompatibility between AT32UC3xxxxx and SSD1306 OLED I2C display (both have HW bugs making them incompatible) allowing me to use HW I2C at 400KBaud (~26.6ms per frame). So I decided to rewrite my old driver for this LCD to make advantage of the new speed by adding also filled surfaces (triangles,quads) instead of just lines and patterned lines (which I already implemented).

The problem is the display is 128x64 pixels but no colors or shades of gray just BW on/off

So in order to for example render rotating cube I need to distinguish the surfaces somehow. I was thinking about randomized filling pattern where surface is filled to some percentage instead of color.

Here my current code (Whole lib but without bugs its working as should):

LCD_SSD1306_I2C.h

//------------------------------------------------------------------------------------------
//--- SSD1306 I2C OLED LCD driver ver 2.000 ------------------------------------------------
//------------------------------------------------------------------------------------------
#ifndef _LCD_SSD1306_I2C_h
#define _LCD_SSD1306_I2C_h
//------------------------------------------------------------------------------------------
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR   0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
//------------------------------------------------------------------------------------------
//#define I2C_send(adr,buf,siz){}
//------------------------------------------------------------------------------------------
#ifndef _brv8_tab
#define _brv8_tab
static const U8 brv8[256] =     // 8 bit bit reversal
    {
    0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,
    88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,
    44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,
    114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,
    22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,
    65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,
    185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,
    237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,
    75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,
    183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255
    };
#endif
//------------------------------------------------------------------------------------------
class LCD_SSD1306_I2C           // max 96 lines
    {
public:
    // screen
    int adr;                    // I2C adr
    int xs,ys,sz;               // resoluiton
    U8 _scr[((128*96)>>3)+1];   // screen buffer
    U8 *scr;
    U8 *pyx[96];                // scan lines
    // pattern
    U32 pat,pat_m,pat_b;        // binary pattern,max used bit mask,actual bit mask
    // filling
    U32 seed;
    int bufl[96];
    int bufr[96];

    // system api
    void init(int _adr,int _xs,int _ys);                    // initialize LCD: I2C_adr,xs,ys
    void _command(U8 cmd);                                  // *internal* do not cal it (sends command to LCD over I2C)
    void rfsscr();                                          // copy actual screen buffer to LCD (by I2C)
    // gfx rendering col = <0,1>
    void clrscr();                                          // clear screen buffer
    void rotate(int ang);                                   // rotate 180 deg
    void pixel(int x,int y,bool col);                       // set/res pixel
    bool pixel(int x,int y);                                // get pixel
    void line(int x0,int y0,int x1,int y1,bool col);        // line
    void triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);// triangle
    void quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);
    void rect(int x0,int y0,int x1,int y1,bool col);        // rectangle using diagonal points
    // patern rendering
    void pat_set(char *s);                                  // set binary pattern from bianry number string MSB renders first
    void pat_beg();                                         // set pattern state to start of pattern
    void pat_line(int x0,int y0,int x1,int y1,bool col);
    void pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);
    void pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);
    void pat_rect(int x0,int y0,int x1,int y1,bool col);
    // filled polygons col = <0,255>
    void _fill_line(int x0,int y0,int x1,int y1);           // *internal* do not call it (render line into bufl/bufr)
    void _fill_seed();                                      // *internal* do not call it (reset seed)
    U8   _fill_rand();                                      // *internal* do not call it (get pseudo random number)
    void fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col);
    void fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col);
    // text rendering
    void prnchr(int x,int y,char c);                        // render char at x,y (y is rounded to multiple of 8)
    void prntxt(int x,int y,const char *txt);               // render text at x,y (y is rounded to multiple of 8)
    };
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_command(U8 cmd)
    {
    U8 buf[2]=
        {
        0x00,       // 0x40 data/command
        cmd,
        };
    I2C_send(adr,buf,2);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::init(int _adr,int _xs,int _ys)
    {
    int y;
    adr=_adr;
    xs=_xs;
    ys=_ys;
    sz=xs*(ys>>3);
    const bool _external_Vcc=false;
    // VRAM buffer
    scr=_scr+1;                                         // skip first Byte (VRAM/command selection)
    for (y=0;y<ys;y++) pyx[y]=scr+((y>>3)*xs);          // scanlines for fast direct pixel access
    clrscr();
    // Init sequence
    U8 comPins = 0x02;
    U8 contrast = 0x8F;
         if((xs == 128) && (ys == 32)) { comPins = 0x02; contrast = 0x8F; }
    else if((xs == 128) && (ys == 64)) { comPins = 0x12; contrast = (_external_Vcc) ? 0x9F : 0xCF; }
    else if((xs ==  96) && (ys == 16)) { comPins = 0x02; contrast = (_external_Vcc) ? 0x10 : 0xAF; }
    else {} // Other screens
    static U8 init0[27]=
        {
        0x00,                                           // commands
        SSD1306_DISPLAYOFF,                             // 0xAE
        SSD1306_SETDISPLAYCLOCKDIV,0x80,                // 0xD5
        SSD1306_SETMULTIPLEX,ys-1,                      // 0xA8
        SSD1306_SETDISPLAYOFFSET,0x00,                  // 0xD3 no offset
        SSD1306_SETSTARTLINE | 0x0,                     // line 0
        SSD1306_CHARGEPUMP,(_external_Vcc)?0x10:0x14,   // 0x8D
        SSD1306_MEMORYMODE,0x00,                        // 0x20 horizontal (scanlines)
        SSD1306_SEGREMAP | 0x1,
        SSD1306_COMSCANDEC,
        SSD1306_SETCOMPINS,comPins,
        SSD1306_SETCONTRAST,contrast,
        SSD1306_SETPRECHARGE,(_external_Vcc)?0x22:0xF1, // 0xd9
        SSD1306_SETVCOMDETECT,0x40,                     // 0xDB
        SSD1306_DISPLAYALLON_RESUME,                    // 0xA4
        SSD1306_NORMALDISPLAY,                          // 0xA6
        SSD1306_DEACTIVATE_SCROLL,
        SSD1306_DISPLAYON,                              // Main screen turn on
        };
    I2C_send(adr,init0,sizeof(init0));
    // init default pattern
    pat_set("111100100");
    // clear filling buffers
    for (y=0;y<96;y++)
        {
        bufl[y]=-1;
        bufr[y]=-1;
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::clrscr()
    {
    for (int a=0;a<sz;a++) scr[a]=0x00;
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rotate(int ang)
    {
    U8 a0,a1;
    int x0,y0,x1,y1;
    if (ang==180)
     for (y0=0,y1=ys-8;y0<y1;y0+=8,y1-=8)
      for (x0=0,x1=xs-1;x0<xs;x0++,x1--)
          {
          a0=brv8[pyx[y0][x0]];
          a1=brv8[pyx[y1][x1]];
          pyx[y0][x0]=a1;
          pyx[y1][x1]=a0;
          }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rfsscr()
    {
    static const U8 init1[] =
        {
        0x00,                           // commands
        SSD1306_MEMORYMODE,0,           // horizontal addresing mode
        SSD1306_COLUMNADDR,0,xs-1,      // Column start/end address (0/127 reset)
        SSD1306_PAGEADDR,0,(ys>>3)-1,   // Page start/end address (0 reset)
        };
    I2C_send(adr,(U8*)init1,sizeof(init1));

    _scr[0]=0x40;                       // 0x40 VRAM
    // SW I2C can pass whole VRAM in single packet
//  I2C_send(adr,_scr,sz+1);

    // HW I2C must use packets up to 255 bytes so 128+1 (as TWIM0 on UC3 has 8 bit counter)
    int i,n=128; U8 *p=_scr,a;
    for (i=0;i<sz;i+=n){ a=p[0]; p[0]=0x40; I2C_send(adr,p,n+1); p[0]=a; p+=n; }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pixel(int x, int y,bool col)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;
    // add or remove bit
    if (col) pyx[y][x] |= (1<<(y&7));
    else     pyx[y][x] &= (255)^(1<<(y&7));
    }
//------------------------------------------------------------------------------------------
bool LCD_SSD1306_I2C::pixel(int x, int y)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return false;
    // get bit
    return ((pyx[y][x]>>(y&7))&1);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::line(int x0,int y0,int x1,int y1,bool col)
    {
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,y0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        pixel(x0,y0,col);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_line(int x0,int y0,int x1,int y1,bool col)
    {
    bool ccc;
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,y0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        ccc=(pat&pat_b); ccc^=(!col);
        pat_b>>=1; if (!pat_b) pat_b=pat_m;
        pixel(x0,y0,ccc);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_line(int x0,int y0,int x1,int y1)
    {
    int i,n,cx,cy,sx,sy,*buf;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n)
        {
        if ((y0>=0)&&(y0<ys))
            {
            bufl[y0]=x0;
            bufr[y0]=x0;
            }
        return;
        }
    // target buffer depend on y direction
    if (sy>0) buf=bufl; else buf=bufr;
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        if ((y0>=0)&&(y0<ys)) buf[y0]=x0;
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_seed()
    {
    seed=0x017A357E1;
//  RandSeed=0x017A357E1;
    }
//------------------------------------------------------------------------------------------
U8 LCD_SSD1306_I2C::_fill_rand()
    {
    U32 a,b,c;
    a= seed     &0x0FFFF;
    b=(seed>>16)&0x0FFFF;
    seed<<=11;
    seed^=(a<<16);
    seed&=0x0FFFF0000;
    seed|=b+17;
    return seed&255;
//  return Random(256);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col)
    {
    line(x0,y0,x1,y1,col);
    line(x1,y1,x2,y2,col);
    line(x2,y2,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col)
    {
    pat_line(x0,y0,x1,y1,col);
    pat_line(x1,y1,x2,y2,col);
    pat_line(x2,y2,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col)
    {
    int x,y,X0,X1,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y0,x1,y1);
    _fill_line(x1,y1,x2,y2);
    _fill_line(x2,y2,x0,y0);
    // fill horizontal lines
    _fill_seed();
    for (y=Y0;y<=Y1;y++)
        {
        // x range to render
        X0=bufl[y];
        X1=bufr[y];
        if (X0>X1){ x=X0; X0=X1; X1=x; }
        // clip to screen in y axis
        if ((X1<0)||(X0>=xs)) continue;
        if (X0<  0) X0=   0;
        if (X1>=xs) X1=xs-1;
             if (col==  0) for (x=X0;x<=X1;x++) pixel(x,y,0);
        else if (col==255) for (x=X0;x<=X1;x++) pixel(x,y,1);
        else for (x=X0;x<=X1;x++) pixel(x,y,(_fill_rand()<=col));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col)
    {
    line(x0,y0,x1,y1,col);
    line(x1,y1,x2,y2,col);
    line(x2,y2,x3,y3,col);
    line(x3,y3,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col)
    {
    pat_line(x0,y0,x1,y1,col);
    pat_line(x1,y1,x2,y2,col);
    pat_line(x2,y2,x3,y3,col);
    pat_line(x3,y3,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col)
    {
    int x,y,X0,X1,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    if (Y0>y3) Y0=y3;
    if (Y1<y3) Y1=y3;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y0,x1,y1);
    _fill_line(x1,y1,x2,y2);
    _fill_line(x2,y2,x3,y3);
    _fill_line(x3,y3,x0,y0);
    // fill horizontal lines
    _fill_seed();
    for (y=Y0;y<=Y1;y++)
        {
        // x range to render
        X0=bufl[y];
        X1=bufr[y];
        if (X0>X1){ x=X0; X0=X1; X1=x; }
        // clip to screen in y axis
        if ((X1<0)||(X0>=xs)) continue;
        if (X0<  0) X0=   0;
        if (X1>=xs) X1=xs-1;
             if (col==  0) for (x=X0;x<=X1;x++) pixel(x,y,0);
        else if (col==255) for (x=X0;x<=X1;x++) pixel(x,y,1);
        else for (x=X0;x<=X1;x++) pixel(x,y,(_fill_rand()<=col));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rect(int x0,int y0,int x1,int y1,bool col)
    {
    line(x0,y0,x1,y0,col);
    line(x1,y0,x1,y1,col);
    line(x1,y1,x0,y1,col);
    line(x0,y1,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_rect(int x0,int y0,int x1,int y1,bool col)
    {
    pat_line(x0,y0,x1,y0,col);
    pat_line(x1,y0,x1,y1,col);
    pat_line(x1,y1,x0,y1,col);
    pat_line(x0,y1,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prnchr(int x,int y,char c)
    {
    y&=0xFFFFFFF8;  // multiple of 8
    if ((y<0)||(y>ys-8)) return;
    int i,a;
    a=c; a<<=3;
    for (i=0;i<8;i++,x++,a++)
     if ((x>=0)&&(x<xs))
      pyx[y][x]=font[a];
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prntxt(int x,int y,const char *txt)
    {
    for (;*txt;txt++,x+=8) prnchr(x,y,*txt);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_set(char *s)
    {
    int i=1;
    pat=0;
    if (s!=NULL)
     for (i=0;(*s)&&(i<32);s++,i++)
        {
        pat<<=1;
        if (*s=='1') pat|=1;
        }
    if (!i) i=1;
    pat_m=1<<(i-1);
    pat_beg();
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_beg()
    {
    pat_b=pat_m;
    }
//------------------------------------------------------------------------------------------
#undef I2C_send
//------------------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------------------

Its written for AVR32 studio 2.7 so on platforms where there is no U8/U16/U32 use unsigned int instead of the same (or bigger) bitwidth.

The code is not optimized and is deliberately written in manner it is (not for speed, I use this also on my lectures so students can grasp what I am doing)

Now when I render rotating cube (on win32 VCL test app on PC) using 2D filled quads using this technique:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
const int sz=4;         // LCD pixel size
int xs,ys;              // LCD resolution * sz
int *psz;               // screen pixel to LCD pixel
Graphics::TBitmap *bmp; // screen buffer
//---------------------------------------------------------------------------
typedef BYTE U8; // here use data types you got like unsigned __int8_t ...
typedef WORD U16;
typedef DWORD U32;
void I2C_send(int adr,U8 *buf,int siz){}
//#include "font_8x8.h"  // font file
static U8 font[256<<3]; // empty font instead (no printing used)
#include "LCD_SSD1306_I2C.h"
LCD_SSD1306_I2C lcd;
//---------------------------------------------------------------------------
const float cube_pos[]=
    {
//  x    y    z     //ix
    -1.0,+1.0,-1.0, //0
    +1.0,+1.0,-1.0, //1
    +1.0,-1.0,-1.0, //2
    -1.0,-1.0,-1.0, //3

    -1.0,-1.0,+1.0, //4
    +1.0,-1.0,+1.0, //5
    +1.0,+1.0,+1.0, //6
    -1.0,+1.0,+1.0, //7

    -1.0,-1.0,-1.0, //3
    +1.0,-1.0,-1.0, //2
    +1.0,-1.0,+1.0, //5
    -1.0,-1.0,+1.0, //4

    +1.0,-1.0,-1.0, //2
    +1.0,+1.0,-1.0, //1
    +1.0,+1.0,+1.0, //6
    +1.0,-1.0,+1.0, //5

    +1.0,+1.0,-1.0, //1
    -1.0,+1.0,-1.0, //0
    -1.0,+1.0,+1.0, //7
    +1.0,+1.0,+1.0, //6

    -1.0,+1.0,-1.0, //0
    -1.0,-1.0,-1.0, //3
    -1.0,-1.0,+1.0, //4
    -1.0,+1.0,+1.0, //7
    };

const float cube_nor[]=
    {
//   nx   ny   nz   //ix
     0.0, 0.0,-1.0, //0
     0.0, 0.0,-1.0, //1
     0.0, 0.0,-1.0, //2
     0.0, 0.0,-1.0, //3

     0.0, 0.0,+1.0, //4
     0.0, 0.0,+1.0, //5
     0.0, 0.0,+1.0, //6
     0.0, 0.0,+1.0, //7

     0.0,-1.0, 0.0, //0
     0.0,-1.0, 0.0, //1
     0.0,-1.0, 0.0, //5
     0.0,-1.0, 0.0, //4

    +1.0, 0.0, 0.0, //1
    +1.0, 0.0, 0.0, //2
    +1.0, 0.0, 0.0, //6
    +1.0, 0.0, 0.0, //5

     0.0,+1.0, 0.0, //2
     0.0,+1.0, 0.0, //3
     0.0,+1.0, 0.0, //7
     0.0,+1.0, 0.0, //6

    -1.0, 0.0, 0.0, //3
    -1.0, 0.0, 0.0, //0
    -1.0, 0.0, 0.0, //4
    -1.0, 0.0, 0.0, //7
    };
//---------------------------------------------------------------------------
void  matrix_mul_pos(float *c,const float *a,const float *b)
    {
    float q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
void  matrix_mul_dir(float *c,const float *a,const float *b)
    {
    float q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
void  matrix_mul_mat(float *c,float *a,float *b)
    {
    float q[16];
    q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
    q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
    q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
    q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
    q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
    q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
    q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
    q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
    q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
    q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
    q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
    q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
    q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
    q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
    q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
    q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
    for(int i=0;i<16;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
float deg=M_PI/180.0,angx=0.0,angy=0.0,angz=5.0*deg;
float view_FOVx=128.0*tan(30.0*deg)*0.5;
void obj2scr(int &x,int &y,const float *m,const float *pos)
    {
    float p[3],d;
    x=0; y=0;
    matrix_mul_pos(p,m,pos);
    if (fabs(p[2])>1e-3) d=view_FOVx/p[2]; else d=0.0;
    p[0]*=d; x=2.5*p[0]; x+=64;
    p[1]*=d; y=2.5*p[1]; y+=32;
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    int i,j,n,nz;
    int x,y,x0,y0,x1,y1,x2,y2,x3,y3;
    lcd.clrscr();

    // modelview
    float p[3],c,s,m[16],m0[16]=
        {
         1.0, 0.0, 0.0,0.0,
         0.0, 1.0, 0.0,0.0,
         0.0, 0.0, 1.0,0.0,
         0.0, 0.0, 7.0,1.0,
        };
    c=cos(angx); s=sin(angx);
    float rx[16]= { 1, 0, 0, 0,
                     0, c, s, 0,
                     0,-s, c, 0,
                     0, 0, 0, 1 };
    c=cos(angy); s=sin(angy);
    float ry[16]= { c, 0, s, 0,
                    0, 1, 0, 0,
                   -s, 0, c, 0,
                    0, 0, 0, 1 };
    c=cos(angz); s=sin(angz);
    float rz[16]= { c, s, 0, 0,
                   -s, c, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1 };
    matrix_mul_mat(m,rx,ry);
    matrix_mul_mat(m,m,rz);
    matrix_mul_mat(m,m,m0);
    angx=fmod(angx+1.0*deg,2.0*M_PI);
    angy=fmod(angy+5.0*deg,2.0*M_PI);
    angz=fmod(angz+2.0*deg,2.0*M_PI);

    n=6*4*3;
    for (i=0;i<n;)
        {
        matrix_mul_dir(p,m,cube_nor+i);
        nz=float(-255.0*p[2]*fabs(p[2]));
        obj2scr(x0,y0,m,cube_pos+i); i+=3;
        obj2scr(x1,y1,m,cube_pos+i); i+=3;
        obj2scr(x2,y2,m,cube_pos+i); i+=3;
        obj2scr(x3,y3,m,cube_pos+i); i+=3;
        if (nz>0)
            {
            nz=100+((150*nz)>>8);
            lcd.fill_quad(x0,y0,x1,y1,x2,y2,x3,y3,nz);
            }
        }
    lcd.rfsscr();

    // copy LCD to Canvas to see result
    if (1)
        {
        DWORD col[2]={0x00100018,0x00FFFFFF},*p;
        int x,y,xx,yy;
        for (                           y=0,yy=psz[y];y<ys;y++,yy=psz[y])
         for (p=(DWORD*)bmp->ScanLine[y],x=0,xx=psz[x];x<xs;x++,xx=psz[x])
          p[x]=col[lcd.pixel(xx,yy)];
        }
    Canvas->Draw(0,0,bmp);
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    lcd.init(0x3C,128,64);
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    xs=lcd.xs*sz;
    ys=lcd.ys*sz;
    bmp->SetSize(xs,ys);
    ClientWidth=xs;
    ClientHeight=ys;

    int i,n;
    n=xs; if (n<ys) n=ys;
    psz=new int[n+1];
    for (i=0;i<n;i++) psz[i]=i/sz; psz[n]=0;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    delete[] psz;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------

Just ignore the VCL stuff. The App has single timer which periodically redrawing screen. The lcd needs to be initialized first. The lcd uses I2C_send function to communicate by I2C so you have to implement it in order to use it with real LCD, If you just copy the screen into image or view (emulation) then empty function like I did will suffice. The same goes for printing texts it needs this font (it does not fit in here) so I used empty one (as the example does not print anything anyway).

I got this output (using the randomized shading filling and basic normal shading):

As you can see due to low resolution the cube is barely distinguishable I was hoping for something better.

So finally my question:

How to visually improve this kind of shading

I was thinking about some kind of hatching or predefined patterns more similar to Freescape engine output like this:

Do you have any ideas or pointers how to do this kind of 2D convex polygon filling?

The limitation is low resolution 128x64 1bpp image, low memory usage as target AVR32 UC3 platform have only (16+32+32) KBytes of RAM and in case someone would like to use AVR8 chips then only 2 KByte (you know Arduino use those).

Speed is not major concern as target platform has ~91 MIPS.

I am not very skilled with BW shading (back in the days I used mostly wireframe for BW output) so even hints from experienced users like:

  • how many shades 16/32/256 ?
  • how big patterns 4x4/8x8/16x16 ?
  • how to generate the patterns (hardcoded, or some algo) ?
  • how to deal with polygon movement to avoid noise/flickering (right now I reset Seed on each polygon)

might help me a lot.

Here a sample input image for testing:

解决方案

I managed to get this working. I ended up with hardcoded 17 shade patterns of size 8x8 pixels (created manually in Paint). These are the shades:

From there I just use x,y of rendered pixel mod 8 as coordinate in the LUT of shades. This is the result:

As you can see its much more better than PRNG based shading. Here the updated code:

//------------------------------------------------------------------------------------------
//--- SSD1306 I2C OLED LCD driver ver 2.001 ------------------------------------------------
//------------------------------------------------------------------------------------------
/*
 [Notes]
 + I2C transfere size is not limitted on LCD side
 - No memory address reset command present so any corruption while VRAM transfere permanently damages output untill power on/off but somehow after some timeout or overflow the adress is reseted
 - UC3 HW I2C limits up to 255 bytes per packet
 - UC3 glitch on SDA before ACK which confuse LCD to read instead of write operation and do not ACK
 */
#ifndef _LCD_SSD1306_I2C_h
#define _LCD_SSD1306_I2C_h
//------------------------------------------------------------------------------------------
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR   0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
//------------------------------------------------------------------------------------------
//#define I2C_send(adr,buf,siz){}
//------------------------------------------------------------------------------------------
#ifndef _brv8_tab
#define _brv8_tab
static const U8 brv8[256] =     // 8 bit bit reversal
    {
    0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,
    88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,
    44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,
    114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,
    22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,
    65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,
    185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,
    237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,
    75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,
    183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255
    };
#endif
static const U8 shade8x8[17*8]= // 17 shade patterns 8x8 pixels
    {
      0,  0,  0,  0,  0,  0,  0,  0,
     17,  0,  0,  0, 17,  0,  0,  0,
     17,  0, 68,  0, 17,  0, 68,  0,
     68, 17, 68,  0, 68, 17, 68,  0,
     85,  0,170,  0, 85,  0,170,  0,
     68,170,  0,170, 68,170,  0,170,
     68,170, 17,170, 68,170, 17,170,
     85,136, 85,170, 85,136, 85,170,
     85,170, 85,170, 85,170, 85,170,
     85,238, 85,170,119,170, 85,170,
    221,170,119,170,221,170,119,170,
    221,170,255,170,221,170,255,170,
     85,255,170,255, 85,255,170,255,
    221,119,221,255,221,119,221,255,
    119,255,221,255,119,255,221,255,
    119,255,255,255,119,255,255,255,
    255,255,255,255,255,255,255,255
    };
//------------------------------------------------------------------------------------------
class LCD_SSD1306_I2C           // max 96 lines
    {
public:
    // screen
    int adr;                    // I2C adr
    int xs,ys,sz;               // resoluiton
    U8 _scr[((128*96)>>3)+1];   // screen buffer
    U8 *scr;
    U8 *pyx[96];                // scan lines
    // pattern
    U32 pat,pat_m,pat_b;        // binary pattern,max used bit mask,actual bit mask
    // filling
    int bufl[96];
    int bufr[96];

    // system api
    void init(int _adr,int _xs,int _ys);                    // initialize LCD: I2C_adr,xs,ys
    void _command(U8 cmd);                                  // *internal* do not cal it (sends command to LCD over I2C)
    void rfsscr();                                          // copy actual screen buffer to LCD (by I2C)
    // gfx rendering col = <0,1>
    void clrscr();                                          // clear screen buffer
    void rotate(int ang);                                   // rotate 180 deg
    void pixel(int x,int y,bool col);                       // set/res pixel
    bool pixel(int x,int y);                                // get pixel
    void line(int x0,int y0,int x1,int y1,bool col);        // line
    void triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);// triangle
    void quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);
    void rect(int x0,int y0,int x1,int y1,bool col);        // rectangle using diagonal points
    // patern rendering
    void pat_set(char *s);                                  // set binary pattern from bianry number string MSB renders first
    void pat_beg();                                         // set pattern state to start of pattern
    void pat_line(int x0,int y0,int x1,int y1,bool col);
    void pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col);
    void pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col);
    void pat_rect(int x0,int y0,int x1,int y1,bool col);
    // filled polygons col = <0,255>
    void _fill_line(int x0,int y0,int x1,int y1);           // *internal* do not call it (render line into bufl/bufr)
    void _fill(int Y0,int Y1,U8 col);                       // *internal* do not call it (render bufl/bufr onto screen)
    void fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col);
    void fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col);
    // text rendering
    void prnchr(int x,int y,char c);                        // render char at x,y (y is rounded to multiple of 8)
    void prntxt(int x,int y,const char *txt);               // render text at x,y (y is rounded to multiple of 8)
    };
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_command(U8 cmd)
    {
    U8 buf[2]=
        {
        0x00,       // 0x40 data/command
        cmd,
        };
    I2C_send(adr,buf,2);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::init(int _adr,int _xs,int _ys)
    {
    int y;
    adr=_adr;
    xs=_xs;
    ys=_ys;
    sz=xs*(ys>>3);
    const bool _external_Vcc=false;
    // VRAM buffer
    scr=_scr+1;                                         // skip first Byte (VRAM/command selection)
    for (y=0;y<ys;y++) pyx[y]=scr+((y>>3)*xs);          // scanlines for fast direct pixel access
    clrscr();
    // Init sequence
    U8 comPins = 0x02;
    U8 contrast = 0x8F;
         if((xs == 128) && (ys == 32)) { comPins = 0x02; contrast = 0x8F; }
    else if((xs == 128) && (ys == 64)) { comPins = 0x12; contrast = (_external_Vcc) ? 0x9F : 0xCF; }
    else if((xs ==  96) && (ys == 16)) { comPins = 0x02; contrast = (_external_Vcc) ? 0x10 : 0xAF; }
    else {} // Other screens
    static U8 init0[27]=
        {
        0x00,                                           // commands
        SSD1306_DISPLAYOFF,                             // 0xAE
        SSD1306_SETDISPLAYCLOCKDIV,0x80,                // 0xD5
        SSD1306_SETMULTIPLEX,ys-1,                      // 0xA8
        SSD1306_SETDISPLAYOFFSET,0x00,                  // 0xD3 no offset
        SSD1306_SETSTARTLINE | 0x0,                     // line 0
        SSD1306_CHARGEPUMP,(_external_Vcc)?0x10:0x14,   // 0x8D
        SSD1306_MEMORYMODE,0x00,                        // 0x20 horizontal (scanlines)
        SSD1306_SEGREMAP | 0x1,
        SSD1306_COMSCANDEC,
        SSD1306_SETCOMPINS,comPins,
        SSD1306_SETCONTRAST,contrast,
        SSD1306_SETPRECHARGE,(_external_Vcc)?0x22:0xF1, // 0xd9
        SSD1306_SETVCOMDETECT,0x40,                     // 0xDB
        SSD1306_DISPLAYALLON_RESUME,                    // 0xA4
        SSD1306_NORMALDISPLAY,                          // 0xA6
        SSD1306_DEACTIVATE_SCROLL,
        SSD1306_DISPLAYON,                              // Main screen turn on
        };
    I2C_send(adr,init0,sizeof(init0));
    // init default pattern
    pat_set("111100100");
    // clear filling buffers
    for (y=0;y<96;y++)
        {
        bufl[y]=-1;
        bufr[y]=-1;
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::clrscr()
    {
    for (int a=0;a<sz;a++) scr[a]=0x00;
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rotate(int ang)
    {
    U8 a0,a1;
    int x0,y0,x1,y1;
    if (ang==180)
     for (y0=0,y1=ys-8;y0<y1;y0+=8,y1-=8)
      for (x0=0,x1=xs-1;x0<xs;x0++,x1--)
          {
          a0=brv8[pyx[y0][x0]];
          a1=brv8[pyx[y1][x1]];
          pyx[y0][x0]=a1;
          pyx[y1][x1]=a0;
          }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rfsscr()
    {
    static const U8 init1[] =
        {
        0x00,                           // commands
        SSD1306_MEMORYMODE,0,           // horizontal addresing mode
        SSD1306_COLUMNADDR,0,xs-1,      // Column start/end address (0/127 reset)
        SSD1306_PAGEADDR,0,(ys>>3)-1,   // Page start/end address (0 reset)
        };
    I2C_send(adr,(U8*)init1,sizeof(init1));

    _scr[0]=0x40;                       // 0x40 VRAM
    // SW I2C can pass whole VRAM in single packet
//  I2C_send(adr,_scr,sz+1);

    // HW I2C must use packets up to 255 bytes so 128+1 (as TWIM0 on UC3 has 8 bit counter)
    int i,n=128; U8 *p=_scr,a;
    for (i=0;i<sz;i+=n){ a=p[0]; p[0]=0x40; I2C_send(adr,p,n+1); p[0]=a; p+=n; }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pixel(int x, int y,bool col)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;
    // add or remove bit
    if (col) pyx[y][x] |= (1<<(y&7));
    else     pyx[y][x] &= (255)^(1<<(y&7));
    }
//------------------------------------------------------------------------------------------
bool LCD_SSD1306_I2C::pixel(int x, int y)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return false;
    // get bit
    return ((pyx[y][x]>>(y&7))&1);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::line(int x0,int y0,int x1,int y1,bool col)
    {
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,y0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        pixel(x0,y0,col);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_line(int x0,int y0,int x1,int y1,bool col)
    {
    bool ccc;
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,y0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        ccc=(pat&pat_b); ccc^=(!col);
        pat_b>>=1; if (!pat_b) pat_b=pat_m;
        pixel(x0,y0,ccc);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_line(int x0,int y0,int x1,int y1)
    {
    int i,n,cx,cy,sx,sy,*buf;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n)
        {
        if ((y0>=0)&&(y0<ys))
            {
            bufl[y0]=x0;
            bufr[y0]=x0;
            }
        return;
        }
    // target buffer depend on y direction
    if (sy>0) buf=bufl; else buf=bufr;
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        if ((y0>=0)&&(y0<ys)) buf[y0]=x0;
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill(int Y0,int Y1,U8 col)
    {
    U8 shd;
    int x,y,X0,X1,i;
    // select shade pattern
    i=col;
    if (i< 0) i=0;
    if (i>17) i=17;
    i<<=3;
    // fill horizontal lines
    for (y=Y0;y<=Y1;y++)
        {
        shd=shade8x8[i+(y&7)];
        // x range to render
        X0=bufl[y];
        X1=bufr[y];
        if (X0>X1){ x=X0; X0=X1; X1=x; }
        // clip to screen in y axis
        if ((X1<0)||(X0>=xs)) continue;
        if (X0<  0) X0=   0;
        if (X1>=xs) X1=xs-1;
             if (col==  0) for (x=X0;x<=X1;x++) pixel(x,y,0);
        else if (col==255) for (x=X0;x<=X1;x++) pixel(x,y,1);
        else for (x=X0;x<=X1;x++) pixel(x,y,shd&(1<<(x&7)));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col)
    {
    line(x0,y0,x1,y1,col);
    line(x1,y1,x2,y2,col);
    line(x2,y2,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_triangle(int x0,int y0,int x1,int y1,int x2,int y2,bool col)
    {
    pat_line(x0,y0,x1,y1,col);
    pat_line(x1,y1,x2,y2,col);
    pat_line(x2,y2,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_triangle(int x0,int y0,int x1,int y1,int x2,int y2,U8 col)
    {
    int y,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y0,x1,y1);
    _fill_line(x1,y1,x2,y2);
    _fill_line(x2,y2,x0,y0);
    // fill horizontal lines
    _fill(Y0,Y1,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col)
    {
    line(x0,y0,x1,y1,col);
    line(x1,y1,x2,y2,col);
    line(x2,y2,x3,y3,col);
    line(x3,y3,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,bool col)
    {
    pat_line(x0,y0,x1,y1,col);
    pat_line(x1,y1,x2,y2,col);
    pat_line(x2,y2,x3,y3,col);
    pat_line(x3,y3,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_quad(int x0,int y0,int x1,int y1,int x2,int y2,int x3,int y3,U8 col)
    {
    int y,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    if (Y0>y3) Y0=y3;
    if (Y1<y3) Y1=y3;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y0,x1,y1);
    _fill_line(x1,y1,x2,y2);
    _fill_line(x2,y2,x3,y3);
    _fill_line(x3,y3,x0,y0);
    // fill horizontal lines
    _fill(Y0,Y1,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rect(int x0,int y0,int x1,int y1,bool col)
    {
    line(x0,y0,x1,y0,col);
    line(x1,y0,x1,y1,col);
    line(x1,y1,x0,y1,col);
    line(x0,y1,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_rect(int x0,int y0,int x1,int y1,bool col)
    {
    pat_line(x0,y0,x1,y0,col);
    pat_line(x1,y0,x1,y1,col);
    pat_line(x1,y1,x0,y1,col);
    pat_line(x0,y1,x0,y0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prnchr(int x,int y,char c)
    {
    y&=0xFFFFFFF8;  // multiple of 8
    if ((y<0)||(y>ys-8)) return;
    int i,a;
    a=c; a<<=3;
    for (i=0;i<8;i++,x++,a++)
     if ((x>=0)&&(x<xs))
      pyx[y][x]=font[a];
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prntxt(int x,int y,const char *txt)
    {
    for (;*txt;txt++,x+=8) prnchr(x,y,*txt);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_set(char *s)
    {
    int i=1;
    pat=0;
    if (s!=NULL)
     for (i=0;(*s)&&(i<32);s++,i++)
        {
        pat<<=1;
        if (*s=='1') pat|=1;
        }
    if (!i) i=1;
    pat_m=1<<(i-1);
    pat_beg();
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_beg()
    {
    pat_b=pat_m;
    }
//------------------------------------------------------------------------------------------
#undef I2C_send
//------------------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------------------

I moved all the filling into member function _fill just see how the LUT shade8x8 is used ...

[Edit1] Floyd–Steinberg dithering

Thanks to Scheff I wanted to try this kind of dithering. Sadly his code is not usable for polygon rasterization (due to its specifics and lack of input image) so I implemented in on my own. I struggled a while to make it work properly and the only way ot worked as expected was when:

  • color threshold is 50% of max intensity
  • error propagation is done on signed integers

The second requirement brings huge performance hit as simple bitshift cant be used anymore however I think hardcoded LUTs will overcome this.

Here my current implementation with booth ditherings:

void LCD_SSD1306_I2C::_fill(int Y0,int Y1,U8 col)
    {
    int x,y,X0,X1,i;
    const U8 colmax=17;
    // bayer like dithering using precomputed shader patterns
    if (dither_mode==0)
        {
        U8 shd;
        // select shade pattern
        i=col;
        if (i< 0) i=0;
        if (i>17) i=17;
        i<<=3;
        // fill horizontal lines
        for (y=Y0;y<=Y1;y++)
            {
            shd=shade8x8[i+(y&7)];
            // x range to render
            X0=bufl[y];
            X1=bufr[y];
            if (X0>X1){ x=X0; X0=X1; X1=x; }
            // clip to screen in y axis
            if ((X1<0)||(X0>=xs)) continue;
            if (X0<  0) X0=   0;
            if (X1>=xs) X1=xs-1;
                 if (col==     0) for (x=X0;x<=X1;x++) pixel(x,y,0);
            else if (col==colmax) for (x=X0;x<=X1;x++) pixel(x,y,1);
            else for (x=X0;x<=X1;x++) pixel(x,y,shd&(1<<(x&7)));
            }
        }
    // Floyd–Steinberg dithering
    if (dither_mode==1)
        {
        int c0,c1,c2,rows[256+4],*r0=rows+1,*r1=rows+128+3,*rr,thr=colmax/2;
        // clear error;
        c0=0; for (x=0;x<256+4;x++) rows[x]=0;
        // fill horizontal lines
        for (y=Y0;y<=Y1;y++)
            {
            // x range to render
            X0=bufl[y];
            X1=bufr[y];
            if (X0>X1){ x=X0; X0=X1; X1=x; }
            // clip to screen in y axis
            if ((X1<0)||(X0>=xs)) continue;
            if (X0<  0) X0=   0;
            if (X1>=xs) X1=xs-1;
                 if (col==     0) for (x=X0;x<=X1;x++) pixel(x,y,0);
            else if (col==colmax) for (x=X0;x<=X1;x++) pixel(x,y,1);
            else for (x=X0;x<=X1;x++)
                {
                c0=col; c0+=r0[x]; ; r0[x]=0;
                if (c0>thr){ pixel(x,y,1); c0-=colmax; }
                 else        pixel(x,y,0);
                c2=c0;
                c1=(3*c0)/16; r1[x-1] =c1; c2-=c1;
                c1=(5*c0)/16; r1[x  ] =c1; c2-=c1;
                c1=(  c0)/16; r1[x+1] =c1; c2-=c1;
                              r0[x+1]+=c2;
                }
            rr=r0; r0=r1; r1=rr;
            }
        }
    }

The dither_mode is just int deciding which dithering to use. Here preview for both side by side. On the left is Bayer ditheringlike (with mine painted pattern) and right is the Floyd–Steinberg dithering:

The Floyd–Steinberg dithering sometimes create ugly (parallel lines) pattern in some rotations while the constant patterns look smooth in all cases So I will stick with my original rendering.

这篇关于渲染填充的表面时,在BW显示器上更好地着色的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆