22
\$\begingroup\$

Take a unit circle centered on the origin. In any two neighboring quadrants, mirror the curve of the circle across the lines connecting the circle's x and y intercepts.

With the resulting shape, you can tile the plane:

circle tessellation

I made this image with the awesome 2D physics sandbox Algodoo!

Write a program that outputs an image similar to this one in some common lossless image file format. You may save the image as a file with the name of your choice or you may simply display it. No input should be taken.

Rules:

  • The entire image must be tessellated with the modified-circle tiles using any two visually distinct RGB colors: one for the vertically pointing tiles, one for the horizontally pointing tiles.

  • The radius of the circle tiles should be at least 32 pixels. (The radius in the image above is about 110 pixels.)

  • The image should be at least 4 tiles wide and 4 tiles tall. This, combined with the rule above, means that images can have a minimum size of 256×256 pixels. (The image above is 4 tiles by 4 tiles.)

  • The tessellation may be translated by any amount. For example, the top left corner of the image does not need to be the vertex where tiles meet. (The tessellation should not be rotated, however.)

  • You may use external graphics libraries that have commands for drawing circles and outputting images and the like.

  • The curves really should approximate circles, as can done with the midpoint circle algorithm, which most graphics libraries will do for you.

  • Anti-aliasing around the edges of the tiles is allowed but not required.

The shortest submission in bytes wins.

\$\endgroup\$

11 Answers 11

4
\$\begingroup\$

gs2, 49 bytes

50 31 05 0d 1f 2a 48 0a 1e 2e 40 83 2c e8 64 2d
1e 73 ed 1e 33 40 20 30 9a a2 22 e8 e9 40 20 30
9a 30 40 20 30 ee 40 20 30 12 32 e9 12 32 55 e8
2b

Generates a PBM image:

output

Mnemonics:

# Print header
"P1" space 256 double
2dup new-line

# Make 1/4 circle
64 range dup cartesian-product
square m1 sum sqrt 64 >= m6
64 /

# Make tile
dup reverse + transpose
@2 not m1 m2
dup reverse + transpose
+

# Make quarter of image
dup reverse + z1
dup reverse +

# Loop
2 * m2
2 *

# Format
show-words m1
unlines
\$\endgroup\$
35
\$\begingroup\$

POV-Ray, 199 163

Old version
camera{location -9*z}light_source{-9*z}#declare L=union{#for(X,-9,9,2)#for(Y,-9,9,2)cylinder{X*x+x+Y*y,<.001*pow(-1,(X+Y)/2),0,.1>+X*x+x+Y*y,1}#end#end}object{L pigment{color x}}object{L rotate z*90}

Same output, but golfed down further by using default light/camera, so I dont even need to specify them
#declare L=union{#for(X,-9,9,2)#for(Y,-9,9,2)cylinder{<X+1,Y,9>,<.001*pow(-1,(X+Y)/2),0,.1>+<X+1,Y,9>,1}#end#end}object{L pigment{color rgb x}rotate z*90}object{L}

enter image description here
I am using as many default parameters for camera and lightsource as possible, thats why it is a little dark. Lets ungolf it first

camera{location 9*z look_at 0}
light_source{9*z color 1} 
#declare L=union{
    #for(X,-9,9,2)
        #for(Y,-9,9,2)
            cylinder{<1+X,Y,0>,                                 //central axis, start
                     <1+X,Y,0> + <.001*pow(-1,(X+Y)/2), 0, .1>, //central axis, end
                      1}                                        //radius
        #end         
    #end
}                         
object{L pigment{color x}} // x is the <1,0,0> vector, here interpreted as RGB
object{L rotate<0,0,90>}

It is obvious what is happening once we increase the offset of the cylinder axis and change the perspective

enter image description here

\$\endgroup\$
  • 1
    \$\begingroup\$ Won't the edges be slightly distorted thanks to 3d perspective? \$\endgroup\$ – orlp May 9 '15 at 4:04
  • 6
    \$\begingroup\$ With a height of 0.1 and an offset of 0.001 the disk is tilted by $\phi=\arctan(0.01) = 0.57°$, looking from the top the disks appear squeezed by a factor of $\cos(\phi) = 0.99995$, thats far less than a pixel. \$\endgroup\$ – DenDenDo May 9 '15 at 7:20
  • \$\begingroup\$ @DenDenDo is pov-ray not able to put a camera at infinity? \$\endgroup\$ – Random832 May 10 '15 at 4:03
  • \$\begingroup\$ @Random832 it can, with camera{orthographic location -9z}. But since the scene is basically 2D it makes no difference, you can even render it with a viewing angle 170 without any fisheye distortion in the result. \$\endgroup\$ – DenDenDo May 10 '15 at 9:21
11
\$\begingroup\$

Gnuplot, 182

I noticed that the boundaries between the cells look very sinusoidal, so i went for an analytical solution with a very simple core equation
enter image description here

set view map
set isosamples 900
f(x,y)=.3*sin(x*3.14)+y
splot(ceil(f(x,y))+ceil(f(y,x)))%2?1:NaN   #can only modulo integers

enter image description here
While it looks similar, the circles are way too square. With the same idea, I replace sin by a curve made from concatenated quartercircle-arcs and rotate it 45° by replacing x and y with x+y and x-y

set view map
set samples 800
set isosamples 800
d=.5**.5
c(x,k)=(-1)**k*sqrt(1-(x-d*(1+2*k))**2)-(-1)**k*d  # k-th circle arc
# s(x)=c(x,floor(x/d/2))                           # circlified sinus
# f(x,y)=d*s(x/d)+y
f(x,y)=d*c(x/d,floor(x))+y                         # combined commented functions
splot(ceil(f(x+y,x-y))+ceil(f(x-y,x+y)))%2?1:NaN

enter image description here

\$\endgroup\$
9
\$\begingroup\$

Context Free, 99 bytes

startshape d CF::Tile=[s 4]CF::Symmetry=CF::pmg,0,1,0path d{ARCTO(-1,1,1)ARCTO(1,1,-1)ARCTO(0,0,1)}

You can see the result at the Context Free Gallery.

\$\endgroup\$
  • \$\begingroup\$ Nice one, that's an amazing use of Context Free. :) \$\endgroup\$ – Martin Ender May 9 '15 at 23:45
7
\$\begingroup\$

HTML+JavaScript, 277

<canvas id=C></canvas><script>r=50,C.width=C.height=9*r,T=C.getContext('2d');
for(f=1,P=Math.PI,i=0;i<45;f=-f,i+=i&7?1:2)x=2*r*(i%8-2),y=2*r*(i>>3),T.moveTo(x,y+f*r),
T.arc(x+r,y+f*r,r,P,-f*P/2,f<0),T.arc(x,y,r,0,P,f>0),T.arc(x-r,y+f*r,r,-f*P/2,0,f<0);
T.fill()</script>

To test, save as html file and open with a browser. Or else, run the snippet

r=50,C.width=C.height=9*r,T=C.getContext('2d')
for(f=1,P=Math.PI,i=0;i<45;f=-f,i+=i&7?1:2)
  x=2*r*(i%8-2),y=2*r*(i>>3),
  T.moveTo(x,y+f*r),
  T.arc(x+r,y+f*r,r,P,-f*P/2,f<0),
  T.arc(x,y,r,0,P,f>0),
  T.arc(x-r,y+f*r,r,-f*P/2,0,f<0)
T.fill()
<canvas id=C></canvas>

Due to popular demand, here is the output image. Not so exciting after all...

Tiles

\$\endgroup\$
  • 1
    \$\begingroup\$ You may want to post an image so the code doesn't have to be run each time someone wants to see the output. \$\endgroup\$ – Calvin's Hobbies May 9 '15 at 19:27
  • \$\begingroup\$ @Calvin'sHobbies oh well it's fast enough and run in every modern browser. I'll make the image bigger instead \$\endgroup\$ – edc65 May 9 '15 at 19:34
  • \$\begingroup\$ That's true. I thought it used => like a lot of your posts and would only work in Firefox. But no worries. \$\endgroup\$ – Calvin's Hobbies May 9 '15 at 19:39
  • 1
    \$\begingroup\$ Better reason for posting an image: These snippets don't work very well on mobile :( \$\endgroup\$ – Sp3000 May 9 '15 at 20:05
6
\$\begingroup\$

IDL 8.3, 201 193 183 bytes

The image is output into an IDL graphics window; I took a screenshot, below.

EDIT: thanks to @AlexA. and @Sp3000 for helping me shave some bytes

p=!pi/99*[0:99]
q=p[49:0:-1]
o=p[99:50:-1]
window,xs=(ys=400)
for i=0,24 do cgpolygon,i mod 5*100+50*[cos(p),cos(q)-1,cos(o)+1],i/5*100+(-1)^i*50*[sin(p),sin(q)-1,sin(o)-1],/d,/fi
end

enter image description here

\$\endgroup\$
6
\$\begingroup\$

Mathematica: 86 bytes (or 82 bytes)

Thanks to the infinite @alephalpha for a clever array-based method:

Image@ArrayFlatten@Array[DiskMatrix@32~RotateLeft~32/.a_/;OddQ@+##:>1-Thread@a&,{5,5}]

Inside the array is an anonymous function, which uses a clever trick to add its arguments (+##) and determine whether the sum is odd. That boolean is used as the conditional to a pattern that replaces the whole 'white' tile with the transformed, 'black' tile. From there, ArrayFlatten joins together the tiles and Image displays them.

Note the use of the shorter Thread to replace Transpose. We can still save 4 bytes by using the transpose symbol instead.

Previous: 97 bytes (or 90 bytes)

Image@ArrayFlatten@Partition[
 Join@@Table[{#,1-Transpose@#}&@RotateLeft[DiskMatrix@32,32],{13}],5]

You can reduce the number of bytes by replacing Transpose@# with the superscript-t symbol (codepoint U+F3C7, shortcut ESCtrESC). In UTF-8 that brings the total to 90 bytes in 88 characters.

enter image description here

We start with DiskMatrix, which generates a binary matrix:

DiskMatrix@32 // Image

enter image description here

We then circular-shift the rows of the matrix to produce the unit cell for the tiling:

RotateLeft[DiskMatrix@32, 32] // Image

enter image description here

If the plane is a chessboard, these are the 'white' squares. For the 'black' squares, we need to invert the colors and rotate by 90 degrees. We can invert by subtracting from 1 (1 - 1 -> 0 and 1 - 0 -> 1), and rotate by taking the transpose:

Image /@ {#, 1 - Transpose@#} &@RotateLeft[DiskMatrix@32, 32]

enter image description here

If the dimensions of the image are even (like the minimum size, 4), then a tile on the right edge will be the same as the next one on the left edge. However, adding one tile to get an odd size (5) then concatenating the rows produces a regular alternating pattern.

This suggests that we can get the full image by wrapping a single row of alternating tiles with Partition. We use Table to make a list of 13 black/white tile pairs, and Join to flatten the list of pairs to a list of 26 tiles. Then we Partition the list into a 5 by 5 matrix of tiles (Partition discards the trailing 26th tile):

Map[Image] /@ 
  Partition[
   Join @@ Table[{#, 1 - #\[Transpose]} &@
      RotateLeft[DiskMatrix@32, 32], {13}], 5] // MatrixForm

enter image description here

Finally ArrayFlatten turns the matrix of tile matrices into a flat matrix, and Image displays the result.

Previous: 111 bytes

Image[ArrayFlatten[{{#, #}, {#, #}}] &[
  Join[#, Reverse@#, 2] &[
   Join[1 - Transpose@#, #] &@RotateLeft[DiskMatrix[32], 32]]]]

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Image@ArrayFlatten@Array[RotateLeft[DiskMatrix@32,32]/.a_/;OddQ[+##]:>1-Thread@a&,{5,5}] \$\endgroup\$ – alephalpha May 12 '15 at 5:37
4
\$\begingroup\$

Java, 550 540 508 504 bytes

This is a java applet.

import java.awt.*;public class T extends java.applet.Applet{int a=98,b=49,x,y;public void paint(Graphics g){for(x=0;x<5;x++)for(y=0;y<5;y++)a(g.create(x*a,y*a,a,a),x%2^y%2);}void a(Graphics g,int c){if(c>0){g.translate(a,0);((Graphics2D)g).scale(-1,1);}g.setColor(Color.red);g.fillRect(0,0,b,b);g.fillRect(b,b,b,b);g.setColor(Color.blue);g.fillRect(b,0,b,b);g.fillRect(0,b,b,b);g.fillArc(0,-b,a,a,180,90);g.fillArc(0,b,a,a,0,90);g.setColor(Color.red);g.fillArc(-b,0,a,a,0,-90);g.fillArc(b,0,a,a,90,90);}}

Expanded with boilerplate:

import java.awt.*;
public class T extends java.applet.Applet{
    int a = 98, b = 49, x, y; //Make these larger for better quality pictures. a = b * 2
    public void paint(Graphics g) {
        for (x=0; x < 5; x++)      //Make these larger for more tiles.
            for (y=0; y < 5; y++)  //
                a(g.create(x * a, y * a, a, a), x % 2 ^ y % 2);
    }

    void a(Graphics g, int c) {
        if (c > 0) {
            g.translate(a, 0);
            ((Graphics2D) g).scale(-1, 1);
        }
        g.setColor(Color.red);            //Change colors for nicer looking colors.
        g.fillRect(0, 0, b, b);
        g.fillRect(b, b, b, b);
        g.setColor(Color.blue);
        g.fillRect(b, 0, b, b);
        g.fillRect(0, b, b, b);
        g.fillArc(0, -b, a, a, 180, 90);
        g.fillArc(0, b, a, a, 0, 90);
        g.setColor(Color.red);
        g.fillArc(-b, 0, a, a, 0, -90);
        g.fillArc(b, 0, a, a, 90, 90);
    }
}

Applet: A small application program that can be called up for use while working in another application.

Example image:

enter image description here

Explanation:

This works by using a method to print each tile. Before the method is created, it is given a graphics object that uses a coordinate system centered on the upper left corner of each tile:

To create a tile, we use the following method:

void a(Graphics g, int c) {
    g.setColor(Color.red);
    g.fillRect(0, 0, b, b);
    g.fillRect(b, b, b, b);
    g.setColor(Color.blue);
    g.fillRect(b, 0, b, b);
    g.fillRect(0, b, b, b);
    g.fillArc(0, -b, a, a, 180, 90);
    g.fillArc(0, b, a, a, 0, 90);
    g.setColor(Color.red);
    g.fillArc(-b, 0, a, a, 270, 90);
    g.fillArc(b, 0, a, a, 90, 90);
}

However, every other tile must be reflected horizontally in order to produce the correct image.

To reflect a tile, we simply modify the supplied graphics object with this code:

g.translate(a, 0);
((Graphics2D) g).scale(-1, 1);

Thanks @CoolGuy for 4 bytes.

\$\endgroup\$
  • 1
    \$\begingroup\$ You can golf it more by declaring x and y as fields of the class:int a = 98, b = 49,x,y; \$\endgroup\$ – Spikatrix May 10 '15 at 13:33
4
\$\begingroup\$

Mathematica 299 256

Wordy but it was nice to figure out.

The basic tile is r (shown below),which is a region displayed by RegionPlot. A left-right reflection of the tile is made and joined with r. The two tile assembled figure is then repeated to tile the space.

r

a_~f~b_ := (x + a)^2 + (y + b)^2 <= 1;
a = ImageAssemble;
r = RegionPlot[(0~f~0 && y <= 0 && ! f[-1, 1]) \[Or] (0~f~2 && 
      y >= -2 && ! f[1, 1]), {x, -1, 1}, {y, -2, 0}, Frame -> False,
    BoundaryStyle -> None];
s = ImageCrop@Rasterize@r;
t = s~ImageReflect~Right;
i = a@{s, t};
j = a@{t, s};
a@{k = {i, i, i, i}, m = {j, j, j, j}, k, m, k, m}

tile

\$\endgroup\$
1
\$\begingroup\$

C, 237 209 180 bytes

180 bytes. This version includes changes suggested by edc65 in a comment. It gives 9 compiler warnings when building on a Mac with clang and default options:

a,b,c,d,x,y;main(){for(puts("P1 256 256");b=a+32&64,a<256;++a){for(c=0;d=c+32&64,x=(a&64)-d?31-a&31:a&31,y=(c&64)-b?c&31:31-c&31,c++<256;)putchar(48+(x*x+y*y<962^b==d));puts("");}}

209 bytes, using some suggestions from comments by Martin. Compiles without warnings with clang:

#include <stdio.h>
int a,b,c,d,x,y;int main(){puts("P1 256 256");for(;b=a+32&64,a<256;++a){for(c=0;d=c+32&64,x=(a&64)-d?31-a&31:a&31,y=(c&64)-b?c&31:31-c&31,c<256;++c)putchar(48+(x*x+y*y<962^b==d));puts("");}}

Original version, 237 bytes:

#include <stdio.h>
int main(){puts("P1 256 256");for(int a=0;a<256;++a){int b=a+32&64;for(int c=0;c<256;++c){int d=c+32&64;int x=(a&64)-d?31-a&31:a&31;int y=(c&64)-b?c&31:31-c&31;putchar(48+(x*x+y*y<962^b==d));}puts("");}}

Result (256x256):

enter image description here

Original code with whitespace for better readability:

#include <stdio.h>
int main()
{
    puts("P1 256 256");
    for (int a = 0; a < 256; ++a)
    {
        int b = a + 32 & 64;
        for (int c = 0; c < 256; ++c)
        {
            int d = c + 32 & 64;
            int x = (a & 64) - d ? 31 - a & 31 : a & 31;
            int y = (c & 64) - b ? c & 31 : 31 - c & 31;
            putchar(48 + (x * x + y * y < 962 ^ b == d));
        }
        puts("");
    }
}

This does not use any graphics library, the rendering is completely contained in the code.

The basic idea is to simply loop over all 256x256 pixels, and see if they are inside/outside the circular arc of the 32x32 sub-square they are in. The bottom 5 bits of the overall pixel coordinates define the relative coordinates of the pixel within the sub-square. The inside/outside test of (x, y) being inside the arc with radius r is then the standard:

x * x + y * y < r * r

Most of the logic is for placing the center of the arc in the correct corner of the sub-square, and determining which color is inside/outside.

Some comments on the solution:

  • The code generates the image in the PBM ASCII format. I loaded the result into GIMP, and did a copy&paste into Paint to generate the actual file I posted here. So the format was converted, but the content is exactly as the original output.
  • If you look closely, you may notice that the quality is not great. This is because the inside/outside calculation is done for the corner of the pixel, instead of the pixel center, causing the whole thing to be 1/2 pixel off. I don't think it would be very difficult to do better, but it would make the code somewhat longer. And since there were not specific quality requirements, I believe this is sufficient.
  • Code was compiled using clang on a Mac. Latest version gives warnings, initial version did not.
  • This is the first time I ever attempted one of these, so I probably missed a few tricks to save the last byte possible.
\$\endgroup\$
  • 3
    \$\begingroup\$ Welcome to PPCG! I'm not a big C golfer, but I think I can see a few improvements: group your declarations like int a,b,c,d,x,y; ... I think you might even be able to just do main(a,b,c,d,x,y) I remember something that the default type is int. Once you're rid of that, you can move the assignments to d, x and y into the inner for's increment statement like d=c+32&64,...,++c (probably even move the ++ into some other place where you mention c anyway), and then you can omit the braces of the inner for. Nice job, btw! :) \$\endgroup\$ – Martin Ender May 9 '15 at 23:43
  • \$\begingroup\$ Thanks! I saw the trick with declaring arguments without types in a list of tips, but it seemed so dirty that I couldn't get myself to go there. ;) I don't think having non-standard arguments for main() is standard compliant. I should certainly group the declarations. And moving the increments will save a couple of bytes, too. The puts() for the newline is in the outer loop, so I'm not sure if I can get rid of the braces. \$\endgroup\$ – Reto Koradi May 9 '15 at 23:56
  • \$\begingroup\$ We're usually okay with it as long as it compiles in some common compiler (so it doesn't have to be entirely standard C). Also yeah I don't think you can get rid of the outer braces, but you should be able, to remove the inner ones. \$\endgroup\$ – Martin Ender May 9 '15 at 23:59
  • \$\begingroup\$ Got it down to 210 bytes. Thanks for the ideas. \$\endgroup\$ – Reto Koradi May 10 '15 at 0:36
  • 1
    \$\begingroup\$ Hints: stdio not needed, use default declaration of function. int is default for globals and can be omitted (variables and main). First puts can go inside the for. c var is not used inside the inner loop, so increment in condition. 180: a,b,c,d,x,y;main(){for(puts("P1 256 256");b=a+32&64,a<256;++a){for(c=0;d=c+32&64,x=(a&64)-d?31-a&31:a&31,y=(c&64)-b?c&31:31-c&31,c++<256;)putchar(48+(x*x+y*y<962^b==d));puts("");}} (compiles with many warnings but runs) \$\endgroup\$ – edc65 May 10 '15 at 1:01
0
\$\begingroup\$

C# lots

work in progress : attempting this in WPF but note sure it fits the output rules?

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace circleDraw
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            int w = 6;
            double s = 200;
            SolidColorBrush[] c = new SolidColorBrush[2];
            c[0] = Brushes.Green;
            c[1] = Brushes.Red;

            var g = new Canvas();
            g.Background = Brushes.Green;
            this.Content = g;

            for (int i = 0; i < w*w; i++) {
                Rectangle r = new Rectangle();
                r.Width = s / 2;
                r.Height = s / 2;
                r.Fill = c[(i % 4)/2];

                Ellipse e = new Ellipse();
                e.Fill = c[1 - (i % 4)/2];
                e.Width = s;
                e.Height = s;
                e.Clip = new RectangleGeometry(new Rect(0, 0, s / 2, s / 2));
                Canvas.SetTop(e, s * (i/w)/2);
                Canvas.SetLeft(e, s * (i%w)/2);
                Canvas.SetTop(r, s * (i / w) / 2);
                Canvas.SetLeft(r, s * (i % w) / 2);
                TransformGroup p = new TransformGroup();
                RotateTransform t = new RotateTransform(90*i,s/4,s/4);
                ScaleTransform y = new ScaleTransform(1,Math.Pow(-1,((i / w) % 2)),s/4,s/4);
                p.Children.Add(t);
                p.Children.Add(y);
                e.RenderTransform = p;
                g.Children.Add(r);
                g.Children.Add(e);
            }
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.