找回密码
 立即注册
首页 业界区 科技 随机 terazzo 生成器

随机 terazzo 生成器

官厌 2025-6-7 10:19:34
前言文章基本思路来自:James Routley
其中,判断点是否在多边形内的算法来自:PNPOLY – Point Inclusion in Polygon Test – WR Franklin (WRF)
本代码中的颜色来自于:RGB颜色值与十六进制颜色码转换工具,并使用RGB与十六进制与RGB565颜色转换工具转换为RGB565代码。(坦白说,我还挺喜欢这些颜色的,比莫奈色系要更鲜艳一点)
想要实现本文代码,你至少需要自己实现有如下功能的函数:绘制直线、绘制点、随机数。

Terazzo 是一种通过将小的彩色碎屑粘合在水泥中制成的材料。凝固后,对其进行切割和抛光,露出由木屑制成的图案。它在用为装饰性随机背景时有不错的表现。
本程序的原理:通过随机产生不重叠的圆,并在圆内内接一个多边形,即可确保生成的多边形是凸多边形且互不交叉。
  图为原理演示图。由于画圆函数无法正确处理负数坐标,出现了重叠的情况。
1.jpg
  以及它的最终效果看起来是这样的:
2.jpg

下面是代码:
  terazzo.h
  1. #ifndef TERAZZO_H
  2. #define TERAZZO_H
  3. #include "./BSP/LCD/lcd.h"
  4. #include "./SYSTEM/sys/sys.h"
  5. #include <stdlib.h>
  6. #include <math.h>
  7. #include <stdio.h>
  8. #define Pink                0xfe19  //粉色
  9. #define LavenderBlush       0xff9e  //脸红的淡紫色
  10. #define Thistle             0xddfb  //蓟
  11. #define MediumSlateBlue     0x7b5d  //适中的板岩暗蓝灰色
  12. #define Lavender            0xe73f  //熏衣草花的淡紫色
  13. #define CornflowerBlue      0x64bd  //矢车菊的蓝色
  14. #define LightSteelBlue      0xb63b  //淡钢蓝
  15. #define LightCyan           0xe7ff  //淡青色
  16. #define Auqamarin           0x7ff5  //绿玉\碧绿色
  17. #define Khaki               0xf731  //卡其布
  18. #define Moccasin            0xff36  //鹿皮鞋
  19. #define LightSalmon         0xfd0f  //浅鲜肉(鲑鱼)色
  20. #define LightCoral          0xf410  //淡珊瑚色
  21. typedef struct {
  22.     uint8_t r;
  23.     uint16_t x;
  24.     uint16_t y;
  25. }Circle;
  26. extern uint16_t terazzo_COLORS[13];
  27. /*清空画布*/
  28. void init_terazzo(void);
  29. /* 在指定大小的画布内(尽可能地)画指定数量的多边形 */
  30. void update_terazzo(uint16_t width,uint16_t height,uint16_t num);
  31. #endif
复制代码
terazzo.c
  1. #include "./BSP/terazzo/terazzo.h"
  2. Circle c[150]={0};             //圆集合
  3. uint16_t cnum=0;                //圆个数
  4. uint16_t terazzo_COLORS[13]={Pink,LavenderBlush,Thistle,MediumSlateBlue,Lavender,CornflowerBlue,LightSteelBlue,LightCyan,Auqamarin,Khaki,Moccasin,LightSalmon,LightCoral};
  5. uint16_t p_width=320,p_height=480;    //画布宽度
  6. void init_terazzo(void){
  7.     //c={0};
  8.     cnum=0;
  9.     lcd_clear(g_back_color);
  10. }
  11. int terazzo_Random(int n)
  12. {
  13.     srand(SysTick->VAL+n);                      /* 半主机模式下使用time函数会报错,在这里用系统定时器的值替代 */
  14.     return rand() % 1000;
  15. }
  16. int check_circle(Circle *circle){
  17.     for(int i=0;i<cnum;i++){
  18.         int x=circle->x-c[i].x;
  19.         int y=circle->y-c[i].y;
  20.         if((x*x+y*y)<pow(circle->r+c[i].r,2)){
  21.             //printf("0");
  22.             return 0;
  23.         }
  24.     }
  25.     //printf("check_success\n");
  26.     return 1;
  27. }
  28. /*
  29. 返回一个不与其它圆重叠的圆,如果返回的圆半径为0,说明找不到这种圆
  30. */
  31. Circle create_circle(){
  32.     Circle cir;
  33.     int r=0;
  34.     do{
  35.         cir.r=terazzo_Random(r+=10)%45+10;
  36.         cir.x=terazzo_Random(r+=10)%p_width;
  37.         cir.y=terazzo_Random(r+=10)%p_height;
  38.         
  39.     }while(!check_circle(&cir)&&(r<3000));
  40.     if(!check_circle(&cir)){
  41.         //printf("creat_error\n");
  42.         cir.r=0;
  43.     }
  44.     //printf("\n%d %d\n",cir.x,cir.y);
  45.     //printf("creat_success\n");
  46.     return cir;
  47. }
  48. /*
  49. 填充指定圆里的多边形,采用逐行扫描填充算法,圆的边界坐标用勾股定理获得。
  50. (Bresenham算法没学会)
  51. 按照从左到右、从上到下的方法扫描
  52. 注意,本函数的边界判断有很大漏洞。
  53. */
  54. void terazzo_scan_fill_color(Circle cir,uint16_t color){
  55.     for(int i=0;i<=cir.r;i++){
  56.         if(i<0)i=0;
  57.         if(i>=p_width)i=p_width-1;
  58.         int h =sqrt(cir.r*cir.r-pow(cir.r-i,2)); //计算扫描长度
  59.         uint8_t last_point = 0;
  60.         uint8_t b = 0;
  61.         
  62.         for(int j=cir.y-h;j<cir.y+h;j++){   //左边
  63.             
  64.             int ty = j;
  65.             if(ty<0)ty=0;
  66.             if(ty>=p_height)ty=p_height-1;           //判断是否越界
  67.             
  68.             int tx=cir.x-cir.r+i;
  69.             if(tx<0)tx=0;
  70.             if(tx>=p_width)tx=p_width-1;
  71.             
  72.             if(lcd_read_point(tx,ty)==color){   //扫描到彩色
  73.                 if(!last_point){                //上一个点不是彩色,说明这个点是边界
  74.                     b=!b;                       //切换填色
  75.                     last_point=1;
  76.                 }
  77.             }else{
  78.                 last_point=0;
  79.             }
  80.             if(b){
  81.                 lcd_draw_point(cir.x-cir.r+i,j,color);
  82.             }
  83.         }
  84.         
  85.         b = 0;
  86.         last_point = 0;
  87.         
  88.         for(int j=cir.y-h;j<cir.y+h;j++){   //右边
  89.             
  90.             int ty = j;
  91.             if(ty<0)ty=0;
  92.             if(ty>=p_height)ty=p_height-1;           //判断是否越界
  93.             
  94.             int tx=cir.x+cir.r-i;
  95.             if(tx<0)tx=0;
  96.             if(tx>=p_width)tx=p_width-1;
  97.             
  98.             if(lcd_read_point(tx,ty)==color){ //扫描到彩色
  99.                 if(!last_point){//上一个点不是彩色,说明这个点是边界
  100.                     b=!b;                //切换填色
  101.                     last_point=1;
  102.                 }
  103.                
  104.             }else{
  105.                 last_point=0;
  106.             }
  107.             if(b){
  108.                 lcd_draw_point(cir.x+cir.r-i,j,color);
  109.             }
  110.         }
  111.         printf("fill,%d\n",h);
  112.     }
  113.    
  114. }
  115. /*此算法由W. Randolph Franklin提出*/
  116. /*参考链接:https://wrfranklin.org/Research/Short_Notes/pnpoly.html*/
  117. uint8_t terazzo_pnpoly(int nvert, uint16_t *vertx, uint16_t *verty, uint16_t testx, uint16_t testy)
  118. {
  119.   uint16_t i, j, c = 0;
  120.   for (i = 0, j = nvert-1; i < nvert; j = i++) {
  121.     if ( ((verty[i]>testy) != (verty[j]>testy)) &&
  122.     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
  123.        c = !c;
  124.   }
  125.   return c;
  126. }
  127. /*
  128. 采用判断点是否在多边形的内部,从而上色。
  129. nvert:顶点数
  130. vertx/y:存有顶点坐标的数组
  131. */
  132. void terazzo_vector_fill_color(Circle cir,uint16_t color,uint8_t nvert, uint16_t *vertx, uint16_t *verty){
  133.     for(int i=0;i<=cir.r*cir.r;i++){
  134.         int h =sqrt(cir.r*cir.r-pow(cir.r-i,2)); //计算扫描长度
  135.         for(int j=cir.y-h;j<=cir.y+h;j++){
  136.             
  137.             int ty = j;
  138.             if(ty<0)ty=0;
  139.             if(ty>=p_height)ty=p_height-1;           //判断是否越界
  140.             
  141.             int tx=cir.x+cir.r-i;
  142.             if(tx<0)tx=0;
  143.             if(tx>=p_width)tx=p_width-1;
  144.             
  145.             if(OLED_pnpoly(nvert,(short *)vertx,(short *)verty,tx,j)){
  146.                 //printf("");
  147.                 lcd_draw_point(tx,j,color);
  148.             }
  149.         }
  150.     }
  151. }
  152. /*
  153. 在一个给定的圆里选取随机的点,并按指定的颜色连线
  154. 内部连线方式:先随机产生3~6个弧度值,并按大小排序。
  155.     然后按照排序顺序生成点坐标并连线。
  156. 这样可以确保生成的连线不会交叉。
  157. */
  158. void link_point(Circle *cir,uint16_t color){
  159.     if(cir->r==0){
  160.         //printf("draw_error\n");
  161.         return;
  162.     }
  163.    
  164.     int zeta_num = terazzo_Random(cir->x+cir->y) % 5 + 3;
  165.     float zeta[zeta_num];
  166.    
  167.     for(int i=0;i<zeta_num;i++){
  168.         zeta[i] = terazzo_Random(i+cir->x+cir->y)%628/100.0f;//生成弧度值
  169.     }
  170.    
  171.     /* 选择排序,小的在前 */
  172.     for(int i=0;i<zeta_num;i++){
  173.         int z=zeta_num-1;
  174.         for(int j=zeta_num-1;j>=i;j--){
  175.             if(zeta[z]>zeta[j]){
  176.                 z=j;
  177.             }
  178.         }
  179.         float t=zeta[i];
  180.         zeta[i]=zeta[z];
  181.         zeta[z]=t;
  182.     }
  183.    
  184.     int16_t pointx[zeta_num],pointy[zeta_num];
  185.     /* 生成点坐标 */
  186.     for(int i=0;i<zeta_num;i++){
  187.         pointx[i]=(cir->x-cir->r*cos(zeta[i]));
  188.         pointy[i]=(cir->y-cir->r*sin(zeta[i]));
  189.         if(pointx[i]>=p_width){
  190.             pointx[i]=p_width-1;
  191.         }
  192.         if(pointy[i]>=p_height){
  193.             pointy[i]=p_height-1;
  194.         }
  195.         if(pointx[i]<0){
  196.             pointx[i]=0;
  197.         }
  198.         if(pointy[i]<0){
  199.             pointy[i]=0;
  200.         }
  201.     }
  202.    
  203.     /* 连线 */
  204.     for(int i=0;i<zeta_num;i++){
  205.         lcd_draw_line(pointx[i],pointy[i],pointx[(i+1)%zeta_num],pointy[(i+1)%zeta_num],color);
  206.     }
  207.     terazzo_vector_fill_color(*cir,color,zeta_num,(uint16_t *)pointx,(uint16_t *)pointy);
  208.     //lcd_draw_circle(cir->x,cir->y,cir->r,RED);
  209.     c[cnum++]=*cir;
  210.     //printf("draw:%d\n",cnum);
  211. }
  212. /*
  213. 在指定大小的画布内(尽可能地)画指定数量的多边形
  214. `*/
  215. void update_terazzo(uint16_t width,uint16_t height,uint16_t num){
  216.     p_width=width;
  217.     p_height=height;
  218.     Circle cir;
  219.     for(int i=0;i<num;i++){
  220.         cir=create_circle();
  221.         
  222.         link_point(&cir,terazzo_COLORS[terazzo_Random(i)%13]);
  223.         
  224.     }
  225.     printf("%d\n",cnum);
  226. }
复制代码
勾股定理算圆心距离,与半径和比较判断两圆位置关系。
Circle create_circle();
  1. int check_circle(Circle *circle){
  2.     for(int i=0;i<cnum;i++){
  3.         int x=circle->x-c[i].x;
  4.         int y=circle->y-c[i].y;
  5.         if((x*x+y*y)<pow(circle->r+c[i].r,2)){
  6.             return 0;
  7.         }
  8.     }
  9.     return 1;
  10. }
复制代码
值得注意的是对生成的弧度值进行排序:这是为了防止在连线的时候生成形如沙漏的自交叉的多边形。
如果你不想生成过于细长的形状,可以在函数第16行改为【上一次产生的弧度值+随机值+合适的常数】来规定两个点之间的最小距离。注意别超过2Π。
void update_terazzo(uint16_t width,uint16_t height,uint16_t num);

[code]/* 在指定大小的画布内(尽可能地)画指定数量的多边形`*/void update_terazzo(uint16_t width,uint16_t height,uint16_t num){    p_width=width;    p_height=height;    Circle cir;    for(int i=0;i
您需要登录后才可以回帖 登录 | 立即注册