Seamless conversion from square to hexagon

23

5

For many games played on a grid, hexagons are the Clearly Superior Choice™. Unfortunately, many free game art sites only have seamless tile sets for square maps. On a past project, I used some of these, and manually converted them to hexagons.

However, I've gotten lazy in my old age. It should be easy to automate the process with a small script.

However, I've gotten lazy in my old age. So I'm outsourcing it to you, and disguising it as a code golf challenge1.


Input

Input is a square image in any common image format capable of 24-bit RGB color. You may also take a filename as input instead of the image data itself.

You may assume the image is square, and that the side length is a multiple of four.

Output

Output is the input tile, but converted to a hexagon (the image itself will be square, with transparent areas). You may save it to a file or display to the screen.

Again, any common image format will do. If the format you're using supports transparency, background areas must be transparent. If it doesn't, you may use color #FF00FF (that horrible fuchsia one) as a stand-in.

Method

So how do we do it? The method I use2 squashes the image a bit vertically, but overall it looks pretty good for most things. We'll do an example with this input image:

input

  • Scale: Scale the image to a 3:2 ratio. Since our images will be squares, this means you simply scale them to 75% width and 50% height. Our example input is 200x200, so we end up with this 150x100 image:

squashed

  • Tile: Place down copies of your scaled image in a 2x2 grid:

grid

  • Crop: Grab an appropriately sized hexagon from anywhere in this 2x2 grid. Now, for ease of tiling, this hexagon isn't exactly regular. After cropping a square of the original size (here 200x200), you then crop out the corners. The crop lines should run from (roughly) the center of each left/right side to one quarter from the edge on the top/bottom:

hex

And that's your output!

Here's an example of what it might look like when tiled (zoomed out here):

tiled

This is code golf, so shortest code in bytes wins. Standard loopholes apply, etc and so on.


1 Feel free to believe this or not.
2 Method one from this helpful site.

Geobits

Posted 2015-10-16T15:08:56.980

Reputation: 19 061

13This question is begging for a Hexagony answer. – Sanchises – 2015-10-16T15:12:54.083

22@sanchises Good luck. – Martin Ender – 2015-10-16T15:13:50.217

17That is where the second part of HexAgony becomes important. – flawr – 2015-10-16T15:20:45.823

-1 for saying fuchsia is horrible :P – mbomb007 – 2015-10-16T16:18:23.533

@mbomb007 Not all fuchsia is horrible. That particular shade, used for so many old sprite sheets, makes my eyes tingle. I totally get why it's used, but... – Geobits – 2015-10-16T16:20:25.903

@Geobits Any color would probably make your eyes tingle when used in place of transparency for alpha, other than black or white. I'd hate yellow and red more than fuchsia. – mbomb007 – 2015-10-16T16:22:19.700

2@mbomb007 Probably, though the #00FF00 green that's sometimes used instead isn't nearly so bad. Still, I feel like they use fuchsia more often because nobody in their right mind would want that exact color in their sprites, so it's pretty universal :P – Geobits – 2015-10-16T16:26:10.960

@MartinBüttner Sorry, my laptop ran out of page file ink so I can't write new programs. – Sanchises – 2015-10-16T16:40:23.847

3#3 - I wait on this patiently to see the cool algorithms generated... then gently and lovingly "borrow" them for my uses ;-) – scunliffe – 2015-10-16T23:32:17.367

Are there any requirements on how to scale the image down? Is nearest-neighbor OK? – John Dvorak – 2015-10-18T05:54:18.727

@JanDvorak Yes, that's fine. Any common scaling technique is adequate. – Geobits – 2015-10-18T06:05:06.207

Answers

10

Matlab, 223 215 209 184 163 bytes

The rescaling is quite straight forward. For cropping the corners I overlay a coordinate system over the pixels, and make a mask via four linear inequalities, which determine the area of the hexagon.

​l=imread(input(''));
u=size(l,1);
L=imresize(l,u*[2 3]/4);
L=[L,L;L,L];L=L(1:u,1:u,:);
[x,y]=meshgrid(1:u);
r=u/2;x=x*2;
set(imshow(L),'Al',y<x+r&y+x>r&y+x<5*r&y>x-3*r)

enter image description here

PS: This turned into a codegolf nim game with @MartinBüttner's submission: You alternatingly have to make your code shorter (while providing the same functionality) - the one that can make the last 'shortening' wins=)

flawr

Posted 2015-10-16T15:08:56.980

Reputation: 40 560

You can save 5 bytes by changing the resize scale calculation to [k.*[2 3]/4]. – beaker – 2015-10-16T19:38:23.907

4Shouldn't the background areas be fuchsia, not black? – Blackhole – 2015-10-17T13:28:08.663

@Blackhole Thanks for letting me know, I fixed it now, even saved me quite a bunch of bytes=) – flawr – 2015-10-17T21:03:03.207

12

Mathematica, 231 211 209 208 201 188 173 bytes

ImageCrop[ImageCollage@{#,#,#,#},e,Left]~SetAlphaChannel~ColorNegate@Graphics[RegularPolygon@6,ImageSize->1.05e,AspectRatio->1]&@ImageResize[#,{3,2}(e=ImageDimensions@#)/4]&

This is an unnamed function which takes an image object and returns an image object:

enter image description here

I don't think there's a lot to explain here, but some details which are of note:

  • Normally, to tile an image 2x2, you'd use ImageAssemble[{{#,#},{#,#}}], i.e. you hand ImageAssemble a 2x2 matrix with copies of the image. However, there's ImageCollage which is some sort of magic function which tries to arrange a bunch of pictures as "well" as possible (whatever that means... you can even give the individual images weights and stuff). Anyway, it you give it four images of equal size and with equal (or no) weights, it will also arrange them in a 2x2 grid. That allows me to save some bytes for the nesting of the matrix, as well as the function name.
  • The hexagon is rendered as a single polygon via Graphics. I'm using the built-in RegularPolygon@6, but enforce an aspect ratio of 1 to stretch it as necessary. Unfortunately, Graphics needs a couple of expensive options to avoid padding and such, and it also renders black on white instead of the opposite. The result is fixed with ColorNegate and then attached to the image's original channels with SetAlphaChannel.
  • Graphics puts a small amount of padding around the hexagon, but we want the alpha hexagon to cover the full size of the cutout. However, SetAlphaChannel can combine images of different sizes, by centring them on top of each other and cropping to the smallest size. That means, instead of manually setting PlotRangePadding->0, we can simply scale up the hexagon image a bit with ImageSize->1.05e (and we need the `ImageSize option anyway).

Martin Ender

Posted 2015-10-16T15:08:56.980

Reputation: 184 808

5

HTML5 + Javascript, 562 bytes

<html><form><input type=text></form><canvas><script>l=document.body.children;l[0].addEventListener("submit",e=>{e.preventDefault();c=l[1].getContext("2d");i=new Image();i.onload=q=>{l[1].width=l[1].height=d=i.width;c.scale(0.75,0.5);c.drawImage(i,0,0);c.drawImage(i,d,0);c.drawImage(i,0,d);c.drawImage(i,d,d);c.globalCompositeOperation="destination-in";c.scale(1/0.75,2);c.beginPath();c.moveTo(d/4,0);c.lineTo(d/4+d/2,0);c.lineTo(d, d/2);c.lineTo(d/4+d/2, d);c.lineTo(d/4, d);c.lineTo(0, d/2);c.closePath();c.fill();};i.src=e.target.children[0].value;})</script>

Takes input as an image URL via textbox (the URL hopefully counts as a filename). Outputs the data to canvas.

Version which works across all browsers (580 bytes):

<html><form><input type=text></form><canvas><script>l=document.body.children;l[0].addEventListener("submit",function(e){e.preventDefault();c=l[1].getContext("2d");i=new Image();i.onload=function(){l[1].width=l[1].height=d=i.width;c.scale(0.75,0.5);c.drawImage(i,0,0);c.drawImage(i,d,0);c.drawImage(i,0,d);c.drawImage(i,d,d);c.globalCompositeOperation = "destination-in";c.scale(1/0.75,2);c.beginPath();c.moveTo(d/4, 0);c.lineTo(d/4+d/2,0);c.lineTo(d, d/2);c.lineTo(d/4+d/2, d);c.lineTo(d/4, d);c.lineTo(0, d/2);c.closePath();c.fill();};i.src=e.target.children[0].value;})</script>

Test it with the "blocks" image from earlier via this URL: http://i.stack.imgur.com/gQAZh.png

adroitwhiz

Posted 2015-10-16T15:08:56.980

Reputation: 301

Nice work! You could save quite a few bytes by adding id=A to the <form> and id=B to the <canvas>, then replacing l[0] with A and l[1] with B, and removing l=document.body.children;. (Works in Firefox 41; not sure which other browsers support this.) Also, I believe there are a couple of unnecessary semicolons next to right curly brackets, and a few extra spaces. – ETHproductions – 2015-10-17T20:15:13.087

More tips: I believe you could add id=C to the <input>, then replace e.target.children[0] with C. 0.75 is equal to 3/4, 1/0.75 is equal to 4/3, d/4+d/2 is equal to d*3/4, and leading zeroes on the other decimals are not necessary. I also believe you could replace the first c.drawImage with c[p="drawImage"], then every subsequent one with c[p]; you could do the same with c.lineTo. – ETHproductions – 2015-10-17T20:22:09.640

4

Python 2+PIL, 320

Reads the name of the image file from stdin.

from PIL import ImageDraw as D,Image as I
i=I.open(raw_input())
C=A,B=i.size
i,j=i.resize((int(.75*A),B/2)),I.new('RGBA',C)
W,H=i.size
for a,b in[(0,0),(0,H),(W,0),(W,H)]:j.paste(i,(a,b,a+W,b+H))
p,d=[(0,0),(A/4,0),(0,B/2),(A/4,B),(0,B)],lambda p:D.Draw(j).polygon(p,fill=(0,)*4)
d(p)
d([(A-x,B-y)for x,y in p])
j.show()

dieter

Posted 2015-10-16T15:08:56.980

Reputation: 2 010

True, sorry about that, didn't have PIL handy to try and I didn't think about it enough. I still stand by my newline statement though :P – FryAmTheEggman – 2015-10-16T21:42:55.070

2

PHP, 293 bytes

I've added some newlines for readability:

function($t){$s=imagesx($t);imagesettile($i=imagecreatetruecolor($s,$s),$t=imagescale
($t,$a=$s*3/4,$b=$s/2));imagefill($i,0,0,IMG_COLOR_TILED);$z=imagefilledpolygon;$z($i,
[0,0,$s/4,0,0,$b,$s/4,$s,0,$s],5,$f=imagecolorallocate($i,255,0,255));$z($i,[$s,0,$a,
0,$s,$b,$a,$s,$s,$s],5,$f);return$i;}

Here is the ungolfed version:

function squareToHexagon($squareImage)
{
    $size = imagesx($squareImage);
    $tileImage = imagescale($squareImage, $size * 3/4, $size/2);

    $hexagonImage = imagecreatetruecolor($size, $size);
    imagesettile($hexagonImage, $tileImage);
    imagefill($hexagonImage, 0, 0, IMG_COLOR_TILED);

    $fuchsia = imagecolorallocate($hexagonImage, 255, 0, 255);
    imagefilledpolygon(
        $hexagonImage,
        [
            0,       0,
            $size/4, 0,
            0,       $size/2,
            $size/4, $size,
            0,       $size,
        ],
        5,
        $fuchsia
    );
    imagefilledpolygon(
        $hexagonImage,
        [
            $size,       0,
            $size * 3/4, 0,
            $size,       $size/2,
            $size * 3/4, $size,
            $size,       $size,
        ],
        5,
        $fuchsia
    );

    return $hexagonImage;
}

header('Content-type: image/gif');
$squareImage = imagecreatefrompng('squareImage.png');
$hexagonImage = squareToHexagon($squareImage);
imagegif($hexagonImage);

Blackhole

Posted 2015-10-16T15:08:56.980

Reputation: 2 362