9
\$\begingroup\$

I've been a fan of My Little Pony: Friendship Is Magic for a long time, and very early on I created a pony version of myself also called Parcly Taxel (warning: may be too cute to look at). The symbol below is her "cutie mark", which you will be drawing here:

Construction

All key points of the cutie mark lie on a rotated hexagonal grid, which will be given coordinates such that (1,0) is 15 degrees counterclockwise from the \$+x\$-axis and (0,1) is likewise 75 degrees counterclockwise.

  • One of the six "arms" of the galaxy is bounded by the following segments in order:
    • A cubic Bézier curve with control points (3,-2), (4,0), (5,4), (-2,12)
    • A straight line from (-2,12) to (3,9)
    • A cubic Bézier curve with control points (3,9), (7,3), (6,-1), (4,-3)
    • A straight line from (4,-3) to (3,-2)
  • The other five arms are obtained by successive 60-degree rotations of the initial arm about the origin. Their fill alternates between RGB #5A318E/(90,49,142) (dark blue) and #54A2E1/(84,162,225) (light blue), with the initial arm getting #5A318E. They have no stroke.
  • Layered above the arms is a regular hexagon centred on the origin and with one vertex at (5,1). It has no fill and is instead stroked with RGB colour #576AB7/(87,106,183) (medium blue), has stroke width 0.5 and has miter join. Parts of the arms should show inside the hexagon.

Task

Draw Parcly Taxel's cutie mark, with the following stipulations:

  • Your program must generate the image by itself and not download it from a website.
  • The image can be saved to a file or piped raw to stdout in any common image file format, or it can be displayed in a window.
  • The image must have (to best effort) the same orientation and aspect ratio as those implied by the construction. Raster images must be at least 400 pixels wide, and an error of 2 pixels/1% is allowed.
  • If your language does not support arbitrary RGB colours you may use any colours reasonably recognisable as light, medium and dark blue.

This is ; fewest bytes wins.

I made this challenge to celebrate reaching 100,000 reputation on the Mathematics Stack Exchange, which I achieved on 27 February 2023.

\$\endgroup\$
4
  • 12
    \$\begingroup\$ Congrats on the 100k on Math SE!!! \$\endgroup\$
    – Aiden Chow
    Mar 1, 2023 at 17:51
  • \$\begingroup\$ mlpvector.club/users/332/cg/v/241-Parcly-Taxel has a reference SVG that's trivially minified to 752 bytes. I can see right off the bat that it's manually drawing each of the swooshes though (instead of e.g. using <use>). Are shape description languages like SVG and TikZ allowed? \$\endgroup\$ Mar 1, 2023 at 20:53
  • 1
    \$\begingroup\$ @JakobLovern Yes and yes (the latter even has a tips question) \$\endgroup\$ Mar 1, 2023 at 23:56
  • \$\begingroup\$ @JakobLovern The provided file over there has quantised coordinates (in accordance with some conventions of the cutie mark section of my Selwyn Git repo). I redrew it exactly according to my own construction for this challenge, just to be sure. \$\endgroup\$ Mar 2, 2023 at 2:57

6 Answers 6

8
\$\begingroup\$

SVG (HTML5), 361 357 337 bytes

<svg viewBox=-25,-25,50,50><g fill=#54A2E1><g id=g><path id=a d=M.4,5.3C5.7,5.7,14.8,5,20.4,-9L21.6,-.4C15.7,8.3,6.6,9,-.1,7.2 /><use href=#a transform=rotate(120) /><use href=#a transform=rotate(240)></g><use href=#g transform=rotate(60) fill=#5A318E /><path d=M1.2,-11.1l9,6.6L9,6.6L-1.2,11.1l-9,-6.6L-9,-6.6Z fill=none stroke=#576AB7>

Edit: Saved 4* 24 bytes thanks to @ccprog. (I would have done it myself but the old version used a lot of integers and I was expecting to have to waste many bytes on the accuracy, but it turns out that e.g. √2(7-4√3) is amazingly close to 5!)

*crossed out 4 is still regular 4

\$\endgroup\$
7
  • \$\begingroup\$ Oh wow, optimizations galore! Love how this builds off the same <use> concept as my answer without being nearly as unwieldy \$\endgroup\$ Mar 2, 2023 at 1:26
  • \$\begingroup\$ -4 bytes if you leave off the ` /` in the last tag before the closing group tag and the ` />` at the end. Also, I don't see a rule for the final orientation of the axes, so you could remove the initial transform=rotate(45) for -20 bytes. \$\endgroup\$
    – ccprog
    Mar 2, 2023 at 2:40
  • \$\begingroup\$ Further, the arm path command can be written as M4,3.46C8,0,14-6.93,8-20.8L15-15.6C17-5.2,11,1.73,5,5.2, and the hexagon as M11-1.73,7,8.66-4,10.4-11,1.73-7-8.66,4-10.4Z (-10 bytes) \$\endgroup\$
    – ccprog
    Mar 2, 2023 at 3:00
  • \$\begingroup\$ @ccprog "The image must have ... the same orientation ..." \$\endgroup\$
    – Neil
    Mar 2, 2023 at 8:46
  • \$\begingroup\$ (I don't like the coordinate optimisation, even though it is valid according to a Stack Overflow answer I just read.) \$\endgroup\$
    – Neil
    Mar 2, 2023 at 8:58
7
\$\begingroup\$

PaperScript, 358 355 bytes

H=(a,b)=>new Point(a+b/2,-b*.75**.5)
for(i=0;6>i++;){
p=new Path(H(4,-3))
p.C=p.cubicCurveTo
p.C(H(6,-1),H(7,3),H(3,9))
p.lineTo(H(-2,12))
p.C(H(5,4),H(4,0),H(3,-2))
p.closed=1
p.fillColor=i%2?"#54A2E1":"#5A318E"
p.pivot=0
p.rotation=60*i-15
}
v=H(5,1)
h=Path.RegularPolygon([0,0],6,v.length)
h.strokeColor="#576AB7"
h.strokeWidth=.5
h.rotation=v.angle+15

You can try it here. Paste in the code, and either add the following lines at the end:

view.center = [0,0]
view.zoom = 10

or use the magnifying glass tool to zoom in on the drawing (which will be partially visible in the top left corner of the canvas).

Ungolfed and commented

// Convert hex coordinates (a, b) to Cartesian coordinates (x, y)
// (without the extra 15-degree rotation, which we will apply later)
function hexPoint(a, b) {
    // a and b represent the magnitudes of two vectors whose
    // sum is the point we want. The a vector points to the right
    // (angle = 0 degrees) and the b vector points up and right
    // (angle = -60 degrees, due to positive y being down in
    // this coordinate system).
    // Thus, x is a * cos(0) + b * cos(-60), which simplifies to:
    var x = a + b / 2;
    // and y is a * sin(0) + b * sin(-60):
    var y = b * -Math.sqrt(3 / 4);
    return new Point(x, y);
}

// Draw the six arms
// We draw each arm using the reference points given, then set its
// fill color and rotate it according to the loop index
for (var i = 0; i < 6; i++) {
    // Start at (4, -3)
    var arm = new Path(hexPoint(4, -3));
    // Draw a cubic Bézier curve from there to (3, 9), with handles
    // at (6, -1) and (7, 3)
    arm.cubicCurveTo(hexPoint(6, -1), hexPoint(7, 3), hexPoint(3, 9));
    // Draw a line from there to (-2, 12)
    arm.lineTo(hexPoint(-2, 12));
    // Draw a cubic Bézier curve from there to (3, -2), with handles
    // at (5, 4) and (4, 0)
    arm.cubicCurveTo(hexPoint(5, 4), hexPoint(4, 0), hexPoint(3, -2));
    // Close the path
    arm.closed = true;
    // Fill with dark blue if i is even, light blue if i is odd
    arm.fillColor = i % 2 ? "#54A2E1" : "#5A318E";
    // Set the pivot point to the origin
    arm.pivot = new Point(0, 0);
    // Pivot the arm around that point 15 degrees counterclockwise
    // and 60 * i degrees clockwise
    arm.rotation = -15 + 60 * i;
}

// Draw the hexagon
// We know one of its vertices is at (5, 1), so we'll use that to
// calculate the right numbers for Paper.js's polygon-drawing
// function and a subsequent rotation
var vertex = hexPoint(5, 1);
// Draw a polygon:
hexagon = Path.RegularPolygon({
    // Centered at the origin
    center: new Point(0, 0),
    // With six sides
    sides: 6,
    // With radius equal to the length of a vector from the origin
    // to the vertex
    radius: vertex.length,
    // Medium blue
    strokeColor: "#576AB7",
    // With 0.5 stroke width
    strokeWidth: 0.5,
    // With these rotations:
    //  30 degrees clockwise because the default orientation is
    //   pointy-topped but we want flat-topped
    //  15 degrees counterclockwise because of the rotation of
    //   the whole figure
    //  An angle equal to the angle a vector from the origin to
    //   the previously calculated vertex makes with the x-axis
    rotation: 30 - 15 + vertex.angle,
    // With miter join style (default, don't need to specify)
});

// Put the origin in the center of the view and zoom in so the
// figure is more visible
view.center = new Point(0, 0);
view.zoom = 10;
\$\endgroup\$
7
\$\begingroup\$

JavaScript (ES6), 273 bytes

Thanks to @Shaggy for suggesting to switch from HTML+JS to JS and saving 2 bytes in the new version

Generates a SVG similar to this one from Parcly Taxel.

f=(s=`<svg viewBox="-22 -23 44 4${a=6}">`)=>a--?f(s+(p='<path fill=')+`#5${a&1?'a318e':'4a2e1'} d="M20.3-9C14.8 5 5.8 5.6.4 5.3L-.1 7.2C6.5 9 15.6 8.4 21.6-.4" transform="rotate(${a*60})"/>`):s+p+`none stroke=#576ab7 d="M-10.2 4.5-9-6.6 1.2-11.1 10.2-4.5 9 6.6-1.2 11.1Z">`

Try it online!

Graphical output

f=(s=`<svg viewBox="-22 -23 44 4${a=6}">`)=>a--?f(s+(p='<path fill=')+`#5${a&1?'a318e':'4a2e1'} d="M20.3-9C14.8 5 5.8 5.6.4 5.3L-.1 7.2C6.5 9 15.6 8.4 21.6-.4" transform="rotate(${a*60})"/>`):s+p+`none stroke=#576ab7 d="M-10.2 4.5-9-6.6 1.2-11.1 10.2-4.5 9 6.6-1.2 11.1Z">`

document.write(f())

\$\endgroup\$
7
  • \$\begingroup\$ Why not simply JavaScript for 289 bytes? \$\endgroup\$
    – Shaggy
    Mar 2, 2023 at 10:54
  • 1
    \$\begingroup\$ @Shaggy Good question... (Thank you!) \$\endgroup\$
    – Arnauld
    Mar 2, 2023 at 11:04
  • \$\begingroup\$ 276 bytes \$\endgroup\$
    – Shaggy
    Mar 2, 2023 at 18:28
  • \$\begingroup\$ Also, if you replace the spaces in the viewBox and d attributes with commas then you should, I believe be able to remove their enclosing quotation marks. \$\endgroup\$
    – Shaggy
    Mar 2, 2023 at 19:08
  • 1
    \$\begingroup\$ Oh, and you should also be able to drop the first closing Z. \$\endgroup\$
    – Shaggy
    Mar 3, 2023 at 12:29
3
\$\begingroup\$

SVG: 737 bytes

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-22 -23 44 46"><defs><path id="s" d="M6 28C4 24 2 16 16 0L6 6C-2 18 0 26 4 30z"/></defs><g transform="rotate(-15)"><use transform="matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#54A2E1"/><use transform="rotate(60) matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#5A318E"/><use transform="rotate(120)matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#54A2E1"/><use transform="rotate(180)matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#5A318E"/><use transform="rotate(240)matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#54A2E1"/><use transform="rotate(300)matrix(1,0,.5,-.87,-24,21)" href="#s" fill="#5A318E"/><path style="fill:none;stroke:#576AB7" d="M-11 1.7-7-8.7l11-1.7 7 8.7L7 8.7l-11 1.7z4"/></g></svg>

Using Parcly Taxel's SVG code here we can get a diff (mine in full color, original in red behind):

enter image description here

I could get it closer with a better transform matrix but that would require a larger file size.

\$\endgroup\$
2
\$\begingroup\$

Pug (HTML), 266 267 bytes

- i=0
svg(viewBox='-25,-25,50,50')
 while i<6
  path(d='M3-2C4,0,5,4-2,12L3,9C7,3,6-1,4-3' fill=`#5${i&1?'4a2e1':'a318e'}`transform=`rotate(${60*i++})matrix(1.93-.52.52-1.93,0,0)`)
 path(d='M1.2-11.1l9,6.6L9,6.5-1.2,11.1l-9-6.6L-9-6.5Z' fill='none' stroke='#576AB7')

Try it online

The coordinate system transformation can be written as a matrix

$$\begin{matrix}\\ 1 & tan(15°) & 0\\ -tan(15°) & -1 & 0\\ 0 & 0 & 1\\ \end{matrix}$$

For a unit length of 2, (so the stroke width of the hexagon is 1), this has to be scaled with \$\frac{2}{cos(15°)}\$.

The path data remain in their original coordinate system as defined for the arms, but to ensure the constant stroke width of the hexagon, pretransformed path data are used.

\$\endgroup\$
2
  • \$\begingroup\$ You have L9,6.5 and L-9-6.6 so your hexagon might be slightly more asymmetric then it needs to be. \$\endgroup\$
    – Neil
    Mar 3, 2023 at 8:32
  • \$\begingroup\$ @Neil corrected. I had inconsistencies anyway because I forgot to correct for the slight increase in unit length from the transform matrix. +6 bytes, unfortunately. \$\endgroup\$
    – ccprog
    Mar 3, 2023 at 13:01
0
\$\begingroup\$

JShell 17: 490 488 + 6 bytes

Requires the JAVASE arg to be passed to JShell. I'm unsure how that's scored, hence the "+6" in the title. Shows the image in a window that might be hard to find at first.

new Frame(){public void paint(Graphics h){var g=(Graphics2D)h;g.rotate(-Math.PI/12);var p=new GeneralPath();for(var i=0;i<6;i++,g.rotate(Math.PI/3,169,304),g.setColor(new Color(5910926-i%2*364205)),p.moveTo(249,96),p.curveTo(309,235,249,304,209,338),p.lineTo(219,355),p.curveTo(279,321,339,252,319,148),p.closePath(),g.fill(p));int[]a={279,238,129,59,99,209},b={287,390,407,320,217,200};g.setColor(new Color(5728951));g.setStroke(new BasicStroke(10));g.draw(new Polygon(a,b,6));}}.show();

A surprising topic for a code golf challenge, nice!
What isn't surprising however is the not very competitive score... but as long as my Java answer isn't twice as long as its competitors, I'm happy with it.

This entry uses precalculated values whereever possible. I lose some precision doing this, but as I'm outputting a raster image I still get a fairly accurate result as this overlayed image shows:

In the end, I was able to gain two bytes by reducing the image size from 900x900 to 450x450, making the accuracy loss more noticeable. Here is the original post in case the error is too big:

new Frame(){public void paint(Graphics h){var g=(Graphics2D)h;g.rotate(-Math.PI/12);var p=new GeneralPath();for(var i=0;i<6;i++,g.setColor(new Color(5910926-i%2*364205)),p.moveTo(465,160),p.curveTo(585,437,465,575,385,644),p.lineTo(405,678),p.curveTo(525,609,645,472,605,264),p.closePath(),g.fill(p),g.rotate(Math.PI/3,305,575));int[]a={525,444,225,85,166,385},b={542,748,782,608,402,368};g.setColor(new Color(5728951));g.setStroke(new BasicStroke(20));g.draw(new Polygon(a,b,6));}}.show();
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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