Steganographic Squares

Your job is to take in a string, and generate an NxN image that represents this string. You must also write the algorithm that takes in the image and turns it back into a string as well. The scoring will be will include the byte count of both algorithms:

"Encryption" Algorithm + "Decryption" Algorithm.

You should post each separately, with byte-counts for both the encryption and decryption algorithms displayed individually.

Example Algorithm

For instance, here's the "Programming Puzzles and Code Golf" using a simple ASCII based steganographic algorithm in the Blue channel:

#2e7250,#6ea972,#04eb6f,#0fc767,#74ab72,#ee6161
#b73b6d,#1aae6d,#f37169,#bda56e,#1fe367,#e99620
#706450,#0d3575,#146b7a,#4ea47a,#2a856c,#95d065
#3f2d73,#cef720,#bab661,#d1b86e,#f22564,#12b820
#0f3d43,#c86e6f,#1ee864,#a66565,#247c20,#c3bb47
#0e296f,#89d46c,#585b66,#c08f20,#455c20,#136f20


Actual Image ( )

You can see the blue channel simply holds the ascii values for this image:

50 =  80(P) 72 = 114(r) 6f = 111(o) 67 = 103(g) 72 = 114(r) 61 =  97(a)
6d = 109(m) 6d = 109(m) 69 = 105(i) 6e = 110(n) 67 = 103(g) 20 =  32( )
50 =  80(P) 75 = 117(u) 7a = 122(z) 7a = 122(z) 6c = 108(l) 65 = 101(e)
73 = 115(s) 20 =  32( ) 61 =  97(a) 6e = 110(n) 64 = 100(d) 20 =  32( )
43 =  67(C) 6f = 111(o) 64 = 100(d) 65 = 101(e) 20 =  32( ) 47 =  71(G)
6f = 111(o) 6c = 108(l) 66 = 102(f) 20 =  32( ) 20 =  32( ) 20 =  32( )


While the rest of the channels hold randomly generated values to "spice up" the variety of colors in the image. When pulling the message back out of the image, we can just simply ignore the other channel values, and pull the hex bit in the blue channel, reconstructing the string:

"Programming Puzzles and Code Golf"


Notice the spaces that were used to pad the string in the square are not included in the final decrypted output. While you must pad the string in the image, you may assume that the input string will not end with spaces.

Rules

• You must encode 1 character per pixel, the channel chosen to encode the char is arbitrary.
• The channels of the other RGB colors must be randomized, other than the one you're choosing to encode the string into; this means your final non-encoded channels would need to be between 0x0000-0xFFFF (randomly chosen).
• Expressing the final result as a 2D array of RGB color values is fine 0x000000-0xFFFFFF, no need to use image creation unless you want to have fun with it or if it's less bytes. If you choose to output as hex strings, prefix the hex string with # E.G. #FFFFFF or #05AB1E. You may separate with tabs, commas, or anything else that would be horizontally sensible, but it must maintain the square pattern; in other words, you must use appropriate newline separation.
• The output must be in a square, and the string must be padded with spaces at the end to accomodate this. This means that N≈SQRT(Input#Length()). If the input length is not a perfect square, you should round up on N and pad with spaces.
• As stated previously, if you are padding with spaces in the image, you must not include the padded characters in the final "decrypted" output.
• You can assume that:
• The input string will not end with spaces.
• The input string will only use printable ASCII characters.
• This is , lowest byte count wins.
• Just to clarify, solutions must encode/decode exactly one character per pixel? Nov 12, 2016 at 16:00
• @ETHproductions that sounds like a good follow-up challenge, but for the purpose of this competition, you pick an encoding channel and encode 1 character per pixel. Nov 12, 2016 at 16:03
• I'm probably not going to use this, but: is it OK to "overpad" the image with more spaces than necessary? And is it OK to assume that the image will have the same amount of overpadding as the encoder would generate?
– user62131
Dec 1, 2016 at 17:48
• @ais523 I cannot see how this type of approach would do anything but require more bytes to implement. I'm going to go with no though, as the challenge is too old to make large changes like that. Dec 1, 2016 at 18:05
• Right, I wasn't sure if it was allowed in the original question, rather than recommending a change. (I was thinking about packing the input into a rectangle, which has easier and thus possibly byte-shorter coordinate calculations than packing it into a square, then padding the rectangle out to a larger square.)
– user62131
Dec 1, 2016 at 18:19

C, 201 (Encoding) + 175 (Decoding) = 376 bytes

To Encode:

E(char*J){size_t L=ceil(sqrt(strlen(J)));int U;srand(time(NULL));for(int i=0;i<L;i++){for(int f=0;f<L;f++){printf("#%02X%02X%02X ",rand()%256,(U<strlen(J))?(int)J[U]:32,rand()%256);U+=1;}printf("\n");}}


Encodes each character of the input string in the green channel of the RGB spectrum while setting the two other channels as random hex values. Takes input through STDIN as a string and outputs to STDOUT a multiline string of hex color code in the shape of a square. Assuming you have Python 3 and ImageMagick installed, and the above file is compiled into a file named a.out in the current working directory (CWD), you can directly get the resulting image, named Output.png, to the CWD from the textual output using the following command:

./a.out "<Multiline Input>"|python3 -c "import sys,subprocess;Input=sys.stdin.read();print('# ImageMagick pixel enumeration: {0},{0},255,rgb\n'.format(len(Input.split('\n')[1].split()))+'\n'.join(['%d,%d:(%d,%d,%d)'%(g,i,int(j[1:][:2],16),int(j[1:][2:4],16),int(j[1:][4:6],16))for g,h in enumerate(Input.split('\n'))for i,j in enumerate(h.split())]))"|convert - -scale 1000% Output.png


Here is a sample output image created by the above commamd using Programming Puzzles and Code Golf as the input string:

To Decode:

D(int c,char**U){char T[c];for(int Y=1;Y<c;Y++){char G[2]={U[Y][3],U[Y][4]};T[Y-1]=(char)strtol(G,NULL,16);}int C=c-1;T[C]='\0';while(T[C]==' '){T[C]='\0';C-=1;}printf("%s\n",T);}


Takes input through STDIN a sequence of space-separated hex color code strings with each one enclosed in double quotes (") (char** argv in main) and also, when called in main, int argc for the integer input. Outputs to STDOUT a single/multi-line string representing the decoded message.

I will try to golf these more over time whenever and wherever I can.

Also, if you same both of the methods into the same file, you can use the following main method to put it all together with each function getting the correct inputs:

int main(int argc,char**argv){if(strcmp(argv[1],"E")==0){Encode(argv[2]);}else{Decode(argc,argv);}}


and using this, for encoding you must provide E as the first argument to call the encoding method followed by the single string argument, whereas for decoding, all you need to provide is the sequence of space-separated hex color code strings with each one enclosed in double-quotes (").

Finally, if you want, you can get the fully prepared, ready-to-use version here, although it is not golfed, but also does not output any warnings or errors upon compilation.

Python 2, 164 160 + 94 93 = 253 bytes

Saved 1+1 byte thanks to Wheat Wizard.

Encoder: string must be enclosed in quotes, e.g. "CodeGolf", output is a color ascii PPM image.

from random import*
s=input()
n=int((len(s)-1)**0.5)+1
s=s.ljust(n*n)
r=randint
print"P3 %d %d 255 "%(n,n)+''.join("%d "*3%(r(0,255),r(0,255),ord(c))for c in s)


Decoder: Takes input filename as command line argument

from sys import*


Usage:

 python golf_stegansquare_enc.py > stega.ppm

python golf_stegansquare_dec.py stega.ppm


Example:

Programming Puzzles and Code Golf

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

• You can remove the space between the close paren and then for Nov 12, 2016 at 19:44
• @ETHproductions: sqrt(25-1) = sqrt(24) < 5 and > 4. int from this is 4 which is then +1ed, so 5 Nov 12, 2016 at 20:00
• Oh, my bad, I didn't see the -1. Nov 12, 2016 at 20:23
• You can remove the space between print and ' in the decoder. I am also pretty sure you could do int((len(s)+1)**.5) to save some bytes.
Dec 6, 2016 at 17:01
• I'm redacting the last sentence of my previous comment, however you can shorten the printing by changing ' '.join("%d %d %d" to ''.join(3*"%d " since I'm pretty sure a trailing space is OK.
Dec 6, 2016 at 17:08

05AB1E, 34 + 12 = 46 bytes

Uses red channel.
05AB1E uses CP-1252 encoding.

Encode:

DgDtî©n-Äð×JvyÇh4F15Ý.Rh«}})'#ì®ä

D                                   # duplicate input
ð×J                        # join that many spaces to end of input
v                       # for each char in string
yÇ                     # get ascii value
h                   # convert to base-16 number
4F                 # 4 times do:
15Ý.Rh           # push random base-16 number
«          # concatenate
}}        # end inner and outer loop
)       # wrap in list
'#ì    # prepend a "#" to each element in list
®ä  # split in pieces round_up(sqrt(len(input))) long


Try it online!

Decode:

˜vy3£¦HçJ}ðÜ

˜               # deep flatten input to a list
v              # for each color in the list
y3£           # take the first 3 chars
¦          # remove the hash sign
H         # convert from base-16 to base-10
ç        # get the ascii char with that value
J       # join to string
}      # end loop
ðÜ    # remove trailing spaces


Try it online!

Alternative padding method with equal byte-count

Dgð×J¹gtî©n£

• I think you have to specifically join on newlines, according to the question? (Your answer will likely beat mine even if it's adapted to do that, though, because I only spent five bytes dealing with that part of the question, and you're ahead by more than that.)
– user62131
Dec 6, 2016 at 5:21
• @ais523: The rules states that 2D arrays were okay. Did I misunderstand that somehow? Dec 6, 2016 at 7:19
• "You may separate with tabs, commas, or anything else that would be horizontally sensible, but it must maintain the square pattern; in other words, you must use appropriate newline separation." strongly implies that it needs to be a string, as 2D arrays don't inherently contain newlines. In other words, I interpreted "array" as describing the shape of the output, not the data type of the output.
– user62131
Dec 6, 2016 at 9:39
• @ais523: I've asked the OP for clarification. As you say, it's not a big change to implement, but maybe you can save some bytes as well if formatting isn't needed. Dec 6, 2016 at 10:21
• @ais523 either way is acceptable. Dec 7, 2016 at 17:24

Scala, 97 + 68 = 165 bytes

Encryption (97 bytes):

s=>s.map(_+((math.random*65535).toInt<<8)).iterator.grouped(math.sqrt(s.size)toInt)withPadding 32


Takes a String and retuns an Iterator of Sequences of Integers.

Decryption (68 bytes):

a=>" +$".r.replaceAllIn(a.flatten.map(h=>(h&0xFF)toChar)mkString,"")  Takes an Iterator of Sequences of Integers and returns a string. Explanation: s=> //define an anonymous function s.map( //map each char of the string _+( //to the ascii value plus (math.random*65535).toInt) //a random integer between 0 and 65535 <<8 //shifted 8 bits to the left ) ) .iterator //create an iterator .grouped( //group them in groups of size... math.sqrt(s.size)toInt //sqrt of the size of the input, rounded up )withPadding 32 //pad with spaces to make a square  . a=> " +$"              //take this string
.r                 //parse it as a regex
.replaceAllIn(     //replace every occurence of the regex in...
a.flatten          //a flattened
.map(h=>           //each element mapped
(h&0xFF)toChar)    //to the character of the lower 8 bits
mkString,          //joined to a string
""               //with an empty string
)


Perl, (103+1) + (36+2) = 142 bytes

Text to image encoder (run with -p for a 1-byte penalty; -p0 (for an additional byte of penalties) is necessary if you want to handle newlines in the input string):

$_.=$"while($a=(length)**.5)=~/\./;$_=unpack"H*";s/../sprintf"#%04x$&,",rand+4**8/eg;s/(.*?\K,){$a}/
/g


Image to text decoder (run with -p0 for a 2-byte penalty):

$\.=chr hex for/..\W/g;$\=~s/ *$//}{  This uses the #abcdef text-based image format, and encodes in the blue channel. Here's an example of a possible output given Programming Puzzles and Code Golf as the input: #b4d250,#bccb72,#43f06f,#4d6767,#74ba72,#269461 #e4f26d,#f63d6d,#701c69,#bbf56e,#6ef967,#d78d20 #4e0d50,#9b2775,#afd37a,#12a47a,#63e46c,#0e9565 #4cad73,#e43420,#6da761,#5a306e,#8fba64,#58f720 #d52443,#b4446f,#fbaf64,#4a4365,#1a5020,#f3ea47 #354c6f,#52cb6c,#11a766,#4c380a,#553820,#b31120  Explanation of the encoder: $_.=$" # append a space ($") to the input ($_) while # as long as the following condition holds: (($a=length)**.5)  # the square root of the input length (save this in $a) =~/\./; # has no decimal points in its string represenation$_=unpack"H*";     # convert the input from base-256 to hexadecimal
s/../              # replace two characters of the input
sprintf          # with a string formed from the template
"#%04x$&,", # four hex digits, the two matched characters, and a comma rand+4**8 # those hex digits are a random number from 0 to 4**8 (= 65536) /eg; # and do this for every non-overlapping match s/(.*? # find the minimum number of characters needed to match \K,) # replacing the part of the match after the last matched comma {$a}/            # a string containing $a commas /gx # with a newline, for every non-overlapping match  I was really happy this use of \K worked; it specifies where to replace, and placing it inside a loop, it seems that the occurrence on the last loop iteration is what counts. So s/(.*?\K,){$a}/\n/g will match a minimum-length string of the form anything comma anything comma … anything comma, that has $a commas, but the actual replaced part of the match will simply be the last comma. This has the effect of replacing every $ath comma with a newline, giving us the square shape for the image.

The big advantage of Perl for this challenge (other than the built-in character string-to-hexadecimal converter, which was incredibly convenient) is that it has a very short decoder (so short, in fact, that although Perl has a builtin for converting hexadecimal to a string, it was shorter not to use it). Here's how it works:

$\.=chr # append to$\ the character code
for/..\W/g;  # of each two characters that appear before a
# non-alphanumeric character (not counting overlapping matches)
$\=~s/ *$//  # delete all spaces at the end of $\ }{ # in this context, this means "implicitly print$\,
# prevent any other implicit printing"


The only instances of two characters immediately before a non-alphanumeric character are the blue channels (which we want to unpack), which appear just before commas and newlines; and the two characters that appear before each # other than the first. We don't want the latter category of matches, but they inevitably overlap the former category, and thus will be excluded by the overlapping matches check.

MySQL, 438 + 237 = 675 bytes

There is a trailing new line at the end of the output, but it does not show up after being decrypted. The hex function (integer overload) would chop off leading 0's, so I had to pad it with a string 0. I could save some bytes if I could declare both functions between the delimiters.

Encrypt

delimiter //create function a(i text)returns text begin declare r int;declare q,p text;while mod(length(i),sqrt(length(i)))<>0 do set i:=concat(i,' ');end while;set r:=1;set q:="";while r<=length(i) do set p:=",";if mod(r,sqrt(length(i)))=0 then set p:="\r\n";end if;set q:=concat(q,'#',right(concat(0,hex(floor(rand()*256))),2),right(concat(0,hex(floor(rand()*256))),2),hex(mid(i,r,1)),p);set r:=r+1;end while;return q;end//
delimiter ;


Decrypt

delimiter //create function b(i text)returns text begin declare x int;declare y text;set x:=0;set y:="";while instr(i,'#')>0 do set i:=substr(i,instr(i,'#')+5);set y:=concat(y,unhex(left(i,2)));end while;return trim(y);end//
delimiter ;


Usage:

select a('test')
select b('#7D1874,#FFB465')
select b(a('test'))


C#, 312 + 142 = 454 bytes

Encoding:

using System;I=>{var r=new Random();int i=I.Length;int N=(int)Math.Floor(Math.Sqrt(i))+1,S=N*N;while(i++<S){I+=' ';}var R="";for(i=0;i<S;){R+=i%N<1&i>0?"\n":i<1?"":" ";R+="#"+r.Next(256).ToString("X").PadLeft(2,'0')+r.Next(256).ToString("X").PadLeft(2,'0')+((int)I[i++]).ToString("X").PadLeft(2,'0');}return R;};


Decoding:

using System;I=>{var s=I.Replace('\n',' ').Split(' ');var R="";foreach(var t in s)R+=(char)System.Convert.ToInt32(t[5]+""+t[6],16);return R.TrimEnd(' ');};


Full program:

using System;
class Steganographic
{
static void Main()
{
Func<string, string> E = null;
Func<string, string> D = null;

E=I=>
{
var r=new Random();
int i=I.Length;
int N=(int)Math.Floor(Math.Sqrt(i))+1,S=N*N;
while(i++<S){I+=' ';}
var R="";
for(i=0;i<S;)
{
R+=i%N<1&i>0?"\n":i<1?"":" ";
}
return R;
};

D=I=>
{
var s=I.Replace('\n',' ').Split(' ');
var R="";
foreach(var t in s)
R+=(char)Convert.ToInt32(t[5]+""+t[6],16);
return R.TrimEnd(' ');
};

string encoded = E("Programming Puzzles and Code Golf");
Console.WriteLine(encoded);
Console.WriteLine(D(encoded));

encoded = E("Hello, World!");
Console.WriteLine(encoded);
Console.WriteLine(D(encoded));

}
}


Mathematica, 111 + 65 = 176 bytes

Encoder

Join[255~RandomInteger~{n=⌈Sqrt@Length@#⌉,n,2},ArrayReshape[#,{n,n,1},32],3]~Image~"Byte"&@*ToCharacterCode


Decoder

StringTrim[""<>FromCharacterCode@ImageData[#,"Byte"][[;;,;;,3]]]&


Processing, 220209 194 + 171167 151 = 391380376361 345 bytes

Update:

Removed useless noStroke() and made both for-loops one-statementers.

Removed useless image(p,0,0);, gave the decryptor the filename as the parameter

Encryption Algorithm

void g(String h){int s=ceil(sqrt(h.length()));for(int y=0,x;y<s;y++)for(x=0;x<s;rect(x,y,1,1),x++)stroke(h.length()>y*s+x?h.charAt(y*s+x):32,random(255),random(255));get(0,0,s,s).save("t.png");}


Calling the function: g("Programming Puzzles and Code Golf");

This is a function that takes in a String, and creates the output, before saving it as t.png. It uses the red value to store the hidden text.

void u(String f){PImage p=loadImage(f);f="";for(int j=0,i;j<p.height;j++)for(i=0;i<p.width;i++)f+=(char)red(p.get(i,j));print(f.replaceAll(" +$",""));}  Call function by: u(file_name); This is also a function that searches for the image specified by the parameter, and then outputs the hidden string (since it is shorter than returning a string). Expanded code (Encryption Algorithm) void g(String h) { int s=ceil(sqrt(h.length())); for(int y=0,x;y<s;y++) for(x=0;x<s;rect(x,y,1,1),x++) stroke(h.length()>y*s+x?h.charAt(y*s+x):32,random(255),random(255)); get(0,0,s,s).save("t.png"); }  The string is passed when the function is called. The first line of the function calculates the side length of the square by taking the ceil of its square root. Then we enter a for-loop, where we set the stroke (the colour of the edge) to have the ASCII value of the character as red, and random values for blue and green. After we do this, we create a rect (rectangle) with width = 1 and height = 1, ie a pixel (for some weird reason, I can't use point properly). In the last line, the resulting image is then saved as t.png. (Decryption Algorithm) void u(String f) { PImage p=loadImage(f); f=""; for(int j=0,i;j<p.height;j++) for(i=0;i<p.width;i++) f+=(char)red(p.get(i,j)); print(f.replaceAll(" +$",""));
}


This function has the name of the file as the parameter (as a string). Then the image in the file is stored in a variable to be used later. After we are done with that, we set the string to "" instead of creating a new string just to hold the hidden string. Then we iterate through the image via two nested for-loops, and we add on to the string the character value of the red value of the pixel. Finally, we print the resulting string after removing leading spaces from it (using a regex). The reason we print the hidden text instead of returning it is because this way it is shorter and we save bytes.

Encrypted challenge raw text:

Jelly, 40 + 20 = 60 bytes in Jelly's codepage

Encoder (text → image):

”#;;ØHX¤¥4¡
»⁶x⁹²¤¤Ob⁴‘ịØHÇ€sj€”,Y
L½Ċç@


Try it online!

Decoder (image → text):

ḣ2ØHiÐ€’ḅ⁴Ọ
ṣ”#Ç€œr⁶


Try it online!

An example output the program could produce (it stores information in the red channel):

#504219,#720200,#6F38F1,#67055F,#7228C7,#61AC95
#6DD797,#6D20CB,#6962FA,#6E69B1,#67C41C,#209436
#50CB19,#75C9FC,#7A1B06,#7A695B,#6C5D5B,#6539A6
#735925,#20C80F,#612C38,#6EBF9E,#64C79E,#200915
#4337C5,#6F4704,#64FB5F,#65B2D1,#20E075,#47BC7C
#6F0C16,#6CD8EF,#66060B,#203C6C,#20D6E9,#20C0D7


In these larger challenges, Jelly's terseness starts to drop off a bit, needing several "structural" characters to resolve parsing ambiguities, but it's nonetheless still very terse. Here's how the encoder works:

Subroutine 1: convert digits to randomly padded hex string
”#;;ØHX¤¥4¡
”#;                     prepend #
¤                parse ØH and X as a unit
;                    append
¥               parse ; and ØHX¤ as a unit
4¡             repeat four times

Subroutine 2: convert string λ to square with size ρ
»⁶x⁹²¤¤Ob⁴‘ịØHÇ€sj€”,Y
⁶                      space
⁹²                   ρ squared
¤                  parse ⁹² as a unit
x                     repeat string (i.e. ρ² spaces)
¤                 parse ⁶x⁹²¤ as a unit
»                       take maximum
Because space has the lowest value of any printable ASCII character,
this has the effect of padding λ to length ρ² with spaces.
O                take codepoints of string
b⁴              convert to base 16
ịØH          use as indexes into a list of hexadecimal digits
‘             0-indexed (Jelly uses 1-indexing by default)
Ç€        run subroutine 1 on each element
s       split into groups of size ρ
€     inside each group
j ”,   join on commas
Y  join on newlines

Main program: basically just calculates ρ and lets subroutine 2 do the work
L½Ċç@
L                       length of input
½                      square rooted
Ċ                     rounded up to the next highest integer
ç@                   call subroutine 2 with the original input and the above


And here's how the decoder works:

Subroutine: convert hexadecimal color string (without #) to character
ḣ2ØHiÐ€’ḅ⁴Ọ
ḣ2                      take first two characters
ØHi                   find indexes in a string of hexadecimal digits
Ð€                 for each of those characters
’                0-indexed (Jelly uses 1-indexing by default)
ḅ⁴              convert from base 16
Ọ             convert integer to character

Main program:
ṣ”#Ç€œr⁶
ṣ”#                     split on # signs
Ç€                   run the subroutine for each element
œr⁶                remove spaces from the right