慢羊羊的空间

无为,无我,无欲,居下,清虚,自然

[译] 短小精悍又不可思议的数学艺术挑战赛及各路大神优秀作品展示 金牌收录

比赛简介

本文翻译了国外 StackExchange 上发起的一个叫做 Tweetable Mathematical Art 的比赛,该比赛以极为精简的 C++ 代码生成各种不可思议的图案。原比赛地址:https://codegolf.stackexchange.com/questions/35569/tweetable-mathematical-art

参赛者需要写三个函数,根据坐标分别返回对应的红、蓝、绿数值:

// 红色
unsigned char RD(int i, int j)
{
	// YOUR CODE HERE
}

// 绿色
unsigned char GR(int i, int j)
{
	// YOUR CODE HERE
}

// 蓝色
unsigned char BL(int i, int j)
{
	// YOUR CODE HERE
}

参数 i、j 表示 x、y 坐标,范围 0~1024,返回值范围 0~255。

例如:RD(100, 20) 返回 50,GR(100, 20) 返回 60,BL(100, 20) 返回 70,那么图像 100, 20 位置的颜色为 RGB(50, 60, 70)。

比赛要求是:每个函数的函数体,不超过 140 字符。程序框架提供了几个可以简化代码的定义,可以直接使用。

参赛者编写的三个函数会被插入到如下代码框架中(为了方便显示,我已经修改为 EasyX 绘图):

// 程序名称:Tweetable Mathematical Art 比赛的 EasyX 版本
// 原比赛地址:https://codegolf.stackexchange.com/questions/35569/tweetable-mathematical-art
//
#include <graphics.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define DIM		1024
#define DM1		(DIM-1)
#define _sq(x)	((x)*(x))							// 平方
#define _cb(x)	abs((x)*(x)*(x))					// 立方的绝对值
#define _cr(x)	(unsigned char)(pow((x),1.0/3.0))	// 立方根(运算结果取整)

unsigned char GR(int,int);

unsigned char BL(int,int);

unsigned char RD(int i, int j)
{
	// YOUR CODE HERE
}

unsigned char GR(int i, int j)
{
	// YOUR CODE HERE
}

unsigned char BL(int i, int j)
{
	// YOUR CODE HERE
}

int main()
{
	initgraph(1024, 1024);
	srand((unsigned)time(NULL));

	DWORD *p = GetImageBuffer(NULL);

	for (int y = 0; y < 1024; y++)
		for (int x = 0; x < 1024; x++)
//			putpixel(x, y, RGB(RD(x, y), GR(x, y), BL(x, y)));					// 逐步显示(慢)
			p[(y << 10) + x] = RD(x, y) << 16 | GR(x, y) << 8 | BL(x, y);		// 直接写显示缓冲区(快)

	FlushBatchDraw();

	getmessage(EX_CHAR);	// 按任意键退出
	closegraph();
	return 0;
}

示例

将示例中的 RD、GR、BL 三个函数,替换到上面的代码框架中,编译执行,即可看到生成的效果图。

示例代码 1:

unsigned char RD(int i,int j)
{
	unsigned short n = (unsigned short)sqrt((double)(_sq(i-DIM/2)*_sq(j-DIM/2))*2.0);
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

unsigned char GR(int i,int j)
{
	unsigned short n = (unsigned short)sqrt((double)(
		(_sq(i-DIM/2)|_sq(j-DIM/2))*
		(_sq(i-DIM/2)&_sq(j-DIM/2))
	));
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

unsigned char BL(int i,int j)
{
	unsigned short n = (unsigned short)sqrt((double)(_sq(i-DIM/2)&_sq(j-DIM/2))*2.0);
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

生成效果:

示例代码 2:

unsigned char RD(int i,int j)
{
	unsigned short n = i&&j?(i%j)&(j%i):0;
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

unsigned char GR(int i,int j)
{
	unsigned short n = i&&j?(i%j)+(j%i):0;
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

unsigned char BL(int i,int j)
{
	unsigned short n = i&&j?(i%j)|(j%i):0;
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
}

生成效果:

各路大神的优秀作品

这个比赛引起了全球很多人的参与,以下是原文中各路大神的作品:

以下是国内大神的作品:

(更新中)

原比赛已经封贴,不再更新。如果你也有优秀的作品,请给我发消息,我把你的大作也加进来。

附注:关于原比赛的一点 bug

原比赛的代码框架如下:

// NOTE: compile with g++ filename.cpp -std=c++11
//    also, if this doesn't work for you, try the alternate char-based version

#include <iostream>
#include <cmath>
#define DIM 1024
#define DM1 (DIM-1)
#define _sq(x)	((x)*(x))							// square
#define _cb(x)	abs((x)*(x)*(x))					// absolute value of cube
#define _cr(x)	(unsigned short)(pow((x),1.0/3.0))	// cube root

unsigned short GR(int,int);
unsigned short BL(int,int);

unsigned short RD(int i,int j){
	// YOUR CODE HERE
}
unsigned short GR(int i,int j){
	// YOUR CODE HERE
}
unsigned short BL(int i,int j){
	// YOUR CODE HERE
}

void pixel_write(int,int);
FILE *fp;
int main(){
	fp = fopen("MathPic","wb");
	fprintf(fp, "P6\n%d %d\n1023\n", DIM, DIM);
	for(int j=0;j<DIM;j++)
		for(int i=0;i<DIM;i++)
			pixel_write(i,j);
	fclose(fp);
	return 0;
}
void pixel_write(int i, int j){
	static unsigned short color[3];
	color[0] = RD(i,j)&DM1;
	color[1] = GR(i,j)&DM1;
	color[2] = BL(i,j)&DM1;
	fwrite(color, 2, 3, fp);
}

框架里面提到的另一个 char 版本是没问题的,存在 bug 的是上面这个 short 版本。

对于 short 版本,原比赛的要求是,每个颜色分量为 10bit,返回值的范围为 [0, 1023],unsigned short 类型,将数据生成到一个 ppm 文件中,再以图片形式显示该文件。

但是,比赛使用 ppm 格式的 P6 编码,颜色数据以二进制形式存储,由于 Windows 和 Linux 存在字节序的差异,导致这里有一个严重的 bug。

例如,10bit 的红色分量返回值为 678,那么对应的 8bit 颜色值应该是:678 * 255 / 1023 = 169。

如果在 Windows 下写入 ppm 文件(P6 编码)并在 Linux 下读取显示 ppm 时,该颜色分量的值会进行如下转换:678 转为 16 进制=> 0x2A6 高低位交换=> 0xA602 转为 10 进制=> 42498 计算=> 42498 * 255 / 1023 = 10593 保存到 8bit 变量中=> 10593 & 0xff = 97。显然,这个差距是巨大且无法接受的。

而该比赛给出的范例,恰恰是利用了这样的 bug 才能得出原文给出的效果。

  • 如果在 Windows 下生成 ppm(P6 编码)并在 Windows 下查看,无法得出指定效果。
  • 如果在 Linux 下生成 ppm(P6 编码)并在 Linux 下查看,无法得出指定效果。
  • 如果在 Windows 下生成 ppm(P3 编码,ASCII 格式)并在 Linux 下查看,无法得出指定效果。

本文在将代码转换成 EasyX 时,对于使用 char 版本框架的代码,无需修改,可以直接使用。对于使用 short 版本框架的代码,为了达成原图效果,需要加几行代码模拟这个 bug,例如原文代码:

unsigned char RD(int i,int j)
{
	return i&&j?(i%j)&(j%i):0;
}

需要修改为:

unsigned char RD(int i,int j)
{
	unsigned short n = i&&j?(i%j)&(j%i):0;
	n &= DM1;
	return (n >> 8 | n << 8) * 255 / 1023;
//	return n * 255 / 1023;					// 有些参赛选手的代码在 Linux 下编译,无需交换高低位,就用这行代码替代上一行
}

这样有可能使代码超出 140 字节,望悉知。

添加评论