请问apple store里的这幅图如何用processing、Photoshop或者AI做出来?
10 个回答
这个问题我也关注好久了。虽然
@Chaojie Mo已经提出了思路,并分享了一个processing的实现方法,但这个实现没有复制原图中圆圈尺寸随距中心距离增大而减小的趋势,稍显可惜。
同时,他的方法中采用了一个圆圈class的阵列,在打靶过程中判断正在生成的圆是否与其他圆相交时,需要将所有圆都循环检测一遍。而每次刷新都会重新绘制所有的圆。我没有实际运行那段code,但稍稍有些担心在圆的数量较多的时候,会不会显著降低打靶速度。
于是在他的思路的基础上,我尝试用一种检测像素的方式来绘制图形。最终结果如下:
思路基本和Chaojie Mo的相同,但是在检测圆是否相交时是检测以目标点为中心,边长为半径(逐渐增长)的方框内的有色点和目标点的距离。这样避免了程序到后期因circle ArrayList增长而导致的性能下降。
我不是程序员我不知道我的改动在处理器这么快以及java框架下是否有意义。。。最初的想法只是想提供一个基于像素的程序,提供一个不同思路。如果上述有错误请多担待。
1 读取图像,初始化窗口,设置图像位置但不绘出,初始化半径最大最小值;
2 读取窗口像素,计算白色点数量,并将像素阵列转化为笛卡尔坐标系(x,y);
3 以randomGaussian生成极坐标的r,logoSize<r<windowDiagonalLength,random生成极坐标的theta,再转换成笛卡尔坐标(x,y)。当坐标不在窗口内或者不在logo内时重新生成坐标。
这样使得点最密集处为靠近logo边缘的
一个环,而不是在logo中心,避免logo边缘圆点不够密。
这种打靶方式相对于Chaojie Mo的方法的缺点是对中心是空的的图形不支持,且不可控,但是贵在省心。按下鼠标可以在两种打靶模式里切换。
4 计算坐标距窗口中心的距离,并据此初始化该点处的半径最大最小值。距中心越远,最小值越小,最大值越接近最小值;
5 从半径 r = minRadius 开始慢慢增大半径,检测rect(x-r, y-r, x+r, y+r) 范围内是否有有色点,且有色点与(x,y)距离是否小于r。
若有,则此时r为(x,y)处能得到的最大半径。以此画圆;
若没有检测到有色点,则以(x,y)处最大半径画圆;
6 在void draw()中,当白色点数量多于一定值时,重复步骤2-5;
以下的code只适用于实心图形。当图形为空心时,可以直接用randomGaussian生成x,y坐标不用极坐标转换。
鉴于样图logo边缘的圆点大小和密度显著高于其他部位,可以在初始化logo时,利用判断圆点相交的类似方法先检测logo的边缘并建立一个边缘像素区域首先填充大圆,再全局填充。
我就是懒没写这个功能,你打我呀~
PImage img;
int RadiusUpperLimit=60;
int RadiusLowerLimit=3;
int space = 0;
float Fade = 0.8;
float blankRate = 1;
float logoSize = 120;
boolean manual = true;
int manualBrushSize = 20;
void setup () {
frameRate(6000);
img = loadImage("apple.jpg");
size (img.width, img.height);
background (255);
img.loadPixels();
colorMode(HSB);
noStroke();
}
void draw () {
if (blankRate > 0.5) {
shoot();
} else {
saveFrame("result#######.png");
noLoop();
}
}
void shoot () {
int x = -1;
int y = -1;
loadPixels();
//Finish condition calculation
int blankN = 0;
for (int i=0; i<pixels.length; i++) {
if (pixels[i] == #FFFFFF) {
blankN += 1;
}
}
blankRate = blankN/float(width*height);
println(blankRate);
//Change linear pixel array index into screen coordinates and pick a random pixel; higher chance in the around the center
color [][] pixelsMatrix = new color[width][height];
for (int i = 0; i < width-1; i++) {
for (int j = 0; j < height-1; j++) {
pixelsMatrix[i][j]=pixels[i+j*width];
}
}
println(manual);
if (manual == false) {
//Using polar coordinates to make a ring of dense dots
if ((x<0) || (x>width-1) || (y<0) || (y>height-1)) {
float r = abs(randomGaussian())*((width+height)/4-logoSize)+logoSize;
float theta = random(0, TWO_PI);
x = int(cos(theta)*r)+width/2;
y = int(sin(theta)*r)+height/2;
} else {
if (brightness(img.pixels[x+y*width])>122) {
float r = abs(randomGaussian())*((width+height)/4-logoSize)+logoSize;
float theta = random(0, TWO_PI);
x = int(cos(theta)*r)+width/2;
y = int(sin(theta)*r)+height/2;
}
}
} else {
x = int((randomGaussian()*manualBrushSize)+mouseX);
y = int((randomGaussian()*manualBrushSize)+mouseY);
}
//Change max radius and min radius according to the distance to the center of the window
float dist = dist(x, y, width/2, height/2);
float percentage = constrain(dist/((dist(width, height, 0, 0)/2)*Fade), 0, 1);
int maxR = int(RadiusUpperLimit*(1-percentage) + RadiusLowerLimit*percentage);
int minR = RadiusLowerLimit;
//Detect the distance between filled pixels and the picked pixel within a square region and determine the radius
int R=0;
search:
for (int r=0; r<=maxR; r++) {
//Growing circle, growing bounding box
for (int i=x-r; i<=x+r; i++) {
for (int j=y-r; j<=y+r; j++) {
if ((i>=0) && (j>=0) && (i<width) && (j<height)) {
if ((color(pixelsMatrix[i][j]) != #FFFFFF && dist(i, j, x, y) <= r+space) || brightness(img.pixels[i+j*width])<122) {
R=r;
break search;
}
if (r==maxR) {
R=r;
break search;
}
}
}
}
}
if (R>=minR) {
fill(random(255), 255, 255);
//fill(img.pixels[x+y*width]);
ellipse(x, y, R, R);
}
}
void keyPressed () {
saveFrame("result#######.png");
}
void mousePressed() {
manual = !manual;
}