Message Boards Message Boards

Creating composite ambigrams from Chinese characters in Voxelverse

Posted 2 years ago

enter image description here

If we were to develop three dimensional voxel shanshui (山水) art, then we would definitely need three dimensional voxelized creator and collector stamps when processing transactions. I almost didn't say anything, but Erik Mahieu and Frederick Wu brought up ambigrams in another recent successful team effort. The purpose of this memo (at min) is to uncover a new mathematical conjecture, which occurs when creating composite ambigrams from Chinese characters in the voxelverse.

Here's the scenario.

An exercise involving a $3 \times 3$ matrix.

九gram = "道江湖金水木山自然";
dat = Rasterize[#] & /@ Characters[九gram];
RastToBitmap[im_, trim_] := With[{
   rect = Map[Mod[Mean[#] + 1, 2] &,
     Round[ImageData[im]], {2}]},
  Map[Join[{0, 0, 0}, #, {0, 0, 0}] &,
    rect][[1 + trim ;; -1 - trim, 1 + trim ;; -1 - trim]]]
bitmaps = RastToBitmap[#, 2] & /@ dat;
bitmapIms = Image[Mod[# + 1, 2]] & /@ bitmaps;
Partition[bitmapIms, 3] // Grid  

nine gram

The mission objective that shows up on the teleprompter is to encipher this matrix into the form of $8$ & an extra perfectly decodable voxel ambigrams. The problem (as mentioned by Fredrick) is that the symbols are too high genus for monotonic data to suffice.

Here's the solution I came up with, using the chosen topology of rows + columns + diagonals + knight moves from Square $1$. The lexicon is over three symbols $\{R,G,B\}$ plus another for clear space. After encoding, we can check partial projections:

Partials[gram1_, gram2_, gram3_] := With[{
   dat123 = Position[Outer[
      gram1[[#1, #2]] gram2[[#2, #3]] gram3[[#3, #1]] & ,
      Range[14], Range[14], Range[14], 1], 1]},
  MapIndexed[Function[{grams, ind},
      Complement[RotateRight[#, ind] & /@ Position[
         Outer[grams[[1, #1, #2]] grams[[2, #2, #3]] & ,
          Range[14], Range[14], Range[14], 1], 1], dat123]
      ][#1, #2[[1]] - 1] &, Partition[{gram1, gram2, gram3}, 2, 1, 1]]]

imDat[partials_] := Outer[Image[ReplacePart[
     Table[1, {i, 1, 14}, {j, 1, 14}],
     # -> 0 & /@ #1[[All, #2]]]] &,
  partials[[{2, 3, 1}]], {{1, 2}, {2, 3}, {3, 1}}, 1]

rowDat = Partition[bitmaps, 3];
colDat = Transpose[Partition[bitmaps, 3]];
xDat = {bitmaps[[{1, 5, 9}]], bitmaps[[{3, 5, 7}]], 
   bitmaps[[{1, 6, 8}]]};

rows = Map[imDat[Partials[Sequence @@ #]] &, rowDat];
cols = Map[imDat[Partials[Sequence @@ #]] &, colDat];
diags = Map[imDat[Partials[Sequence @@ #]] &, xDat];

TableForm /@ rows
TableForm /@ cols
TableForm /@ diags

partials

These matrices in themselves have some interesting structure and are worth more detailed analysis, but for now, we need only one superposition function

Superpose[mat_] :=  Map[ImageMultiply@mat[[Complement[Range[3], {#}], #]] &, Range[3]]
TableForm[Superpose /@ #] & /@ {rows, cols, diags}

valid outs

These look correct, but for sake of rigor ++ QA +

MapThread[ImageSubtract[#1, #2] &,
 {Flatten[Superpose /@ rows], bitmapIms}]

MapThread[ImageSubtract[#1, #2] &,
 {Flatten[Superpose /@ cols],
  Flatten[Transpose[Partition[bitmapIms, 3]]]}]

MapThread[ImageSubtract[#1, #2] &,
 {Flatten[Superpose /@ diags],
  bitmapIms[[{1, 5, 9, 3, 5, 7, 1, 6, 8}]]}]

blackbox1 blackbox2 blackbox3

These $9 \times 3$ black boxes mean proof complete, and it may even be possible to recover the secret extra data "knight's move" by comparing regular first two prints, with odd third (proof unknown). More importantly, since this experiment was successful on 9 varied attempts, we have a new mission objective to prove the conjecture that the same process produces perfectly decodable data for any possible bitmap input.

We can also formulate another similar conjecture for fully layered projections. Using the following functions, we obtain new results in three colors:

Ambigram[TriadData_] := With[
{partials = Partials[Sequence @@ #] &@TriadData},
  Graphics3D[ Transpose[{{Blue, Red, Green}, Map[Cuboid, partials, {2}]}]]]

OrthoProj[Ambigram_] :=  GraphicsGrid[
Transpose[Partition[MapThread[Show[Ambigram,
       Boxed -> False, ViewProjection -> "Orthographic", 
       ViewPoint -> #1, ViewVertical -> #2] &,
     {{{0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}},
      {{-1, 0, 0}, {-1, 0, 0}, {0, -1, 0}, {0, -1, 0}, {0, 0, -1}, {0, 0, -1}}}], 2]]]

RGBResults[TriadData_] := With[{amb = Ambigram[TriadData]},
  GraphicsRow[Show[#, ImageSize -> 400] & /@ {
     Show[amb, ViewProjection -> "Orthographic",
      ViewPoint -> {1, 1, 1}, Boxed -> False], OrthoProj[amb]}]]

amb1

amb2

amb3

amb4

amb5

amb6

amb7

amb8

amb9

Face first projections appear to be correct, but is this always the case? Does correctness of Black and White projections imply correctness of RGB projections? These questions must be answered, and theorems proven before we can even begin to think about making voxel NFTs for certain choices of three ASCII characters (or about 玄学X).

POSTED BY: Brad Klee
12 Replies

Well, as for I-Ching‘s binary, I would say instead of brute force, why not "divide and conquer"?

Some people are not dividers, and others are not conquerors. What is difference anyways? Since you have shown such a beautiful, intricate calendar, here is the return encoding the $24$ solar terms (節氣) $\times$ 64 hexagrams:

Solar Term Calendar

It only has $360$ frames, so in practice I guess we would need a few days out of time. The validating data did show a few errors, but I didn't look to closely. It's probably fine.

My answer wouldn't even think to defeat the rain, nor the reverse. If the yarrow stalks say "be careful", maybe I will, maybe I won't:

SeedRandom["first-class"];
randHex = RandomSample[bitmapsHex, 1][[1]];
RGBResults[ Append[RastToBitmap[Rasterize[Style[#, Large]], 16] & /@ 
   Characters["渾沌"], randHex], 16]

blatant code passing

I noticed more Jitter on the graphics grid, but this time it's my fault. If we are going to have voxel plots as a datatype, then we probably also need standard projection functions to 2D vector graphics. Keep going, I guess, but feel that views programing is just procrastinating on learn more crypto.

POSTED BY: Brad Klee
Posted 2 years ago

The retreat from RGB to black and white for a better visualizing is best choice we have. I have no further comments at the moment. Go on with your crypto or NFT.

POSTED BY: Frederick Wu
Posted 2 years ago

Oh, that is I-Ching‘s binary( 伏羲八卦六十四方位图),I thought it was 图(圖)“plot” in traditional form. Usually we state I Ching‘s binary all together like plot below. enter image description here

Well, as for I-Ching‘s binary, I would say instead of brute force, why not "divide and conquer"?

牛B is 牛逼 in short form, 牛郎 is completely different meaning. ^_^ In modern mandarin, there are more and more imported words or letters. So people are used to mix the Chinese characters with English letters or words. E.g. With the latest updating, Brad's code is 牛B-class(Super cool first-class).

POSTED BY: Frederick Wu

Hi Frederick,

Thanks for the compliment, but why not 牛逼 or 牛郎? Tradition? Don't worry, I have another trigram, which I will release soon, as part of a decryption challenge.

It turns out RastToBitmap does have a problem (try input "i"), but not exactly as you were saying. Here are two updated functions and appropriate usage.

RastToBitmap[im_, len_] := With[{rect =
    Map[Mod[Round[Mean[#]] + 1, 2] &,
     ImageData[ImageCrop[im]], {2}]},
  PadIm[rect, len, {len, len} - Dimensions[rect]]]

Hexagram[uniHex_, scale_] := With[{
   hex = ReplacePart[RastToBitmap[Round[Rasterize[uniHex]], 16], {
      {1, _} -> 1, {16, _} -> 1, {_, 1} -> 1, {_, 16} -> 1}]},
  ArrayFlatten[Map[Table[#, {i, scale}, {j, scale}] &,
    ReplacePart[hex, Join[{#, 3} -> hex[[#, 4]] & /@ Range[16],
      {#, 14} -> hex[[#, -4]] & /@ Range[16]]], {2}]]]

fix

SeedRandom["opinion"]
RandHex =  RandomSample[
   FromCharacterCode[#, "Unicode"] & /@ Range[19904, 19967], 1][[1]]
Image[Hexagram[RandHex, #]] & /@ Range[4]

big family

Interoperability with hexagrams requires content size power of $2$ g.t.e. $16$.

Some chatter today about whether RandomSample could/should be weaponized for whatever imaginary fight tends to be happening again and again. If you're also annoyed by that, use SeedRandom for Reproducibility (almost as important to science as originality). The problem here, with a sample size only $64$, is possibility to brute force search until a desired hit is found. So I just said okay "let's just go from the last word of Frederick's nice post, opinion". Lucky output, because it is possible to get burnt casting yarrow stalks.

POSTED BY: Brad Klee
Posted 2 years ago

Hi Brad,

As today is weekend, I have more time to introduce you another puzzle.

  • In letter form (see attached PDF Escher) enter image description here

He played a global symmetry transformation of letters like N and Z, or V and L(in Dutch version)

  • In Chinese character form (See the ancient Chinese coin image) enter image description here

On the obverse side, 4 Characters share a square in center, that make the four different Chinese characters “唯吾知足”. (Read ordering: from left to right, from top to bottom). It plays a game of rotation and reflection transformation, partially. On the reverse side, two independent characters “长乐”. All together "唯吾知足长乐" is a phrase and make sense, "I am a man who is contented and will be happy."

Hopefully, that will amuse you and inspired us to develop more puzzles from letters or characters.

Attachments:
POSTED BY: Frederick Wu
Posted 2 years ago

Hi Brad,

I found a bug for you.

The pixel data through function bitmapsL should be in dimension {16,16} but it wasn't. It's in {22,23}. So It results the 1st and 2nd character only top-left half-data-plot. enter image description here

At the moment, I just resize the dimension in next line of code, then all the rest works well.

bitmapsL = 
 ImageData[Binarize@ImageResize[Image[#], {16, 16}]] & /@ bitmapsL1

But you need to check and verify the function bitmapsL. I upload the notebook for you.

It might be caused by your changing something, but not all variables are completely updated in your notebook. I recommend you to add a Initializing cell at top for clearing all global variables, then automatically evaluate all cells again.

Clear["Global`*"];

It is not just a compliment, I really think it is 牛B. The old-fashion puzzle are probably unchanged for decades. You are reinventing the puzzle and solve it, Now you try to find a theorem from it. So it is 牛B in my opinion.

Attachments:
POSTED BY: Frederick Wu

Counter

Okay guys, I really think you're hassling me on this one little issue but here is a fix:

PadIm[bitmap_, len_, delta_] :=  With[{FixWidth = Join[
  Table[0, {i, Floor[delta[[2]]/2]}],
       #, Table[0, {i, Ceiling[delta[[2]]/2]}]] & /@ bitmap},
  Join[Table[0, {i, Ceiling[delta[[1]]/2]}, {j, len}],
   FixWidth, Table[0, {i, Floor[delta[[1]]/2]}, {j, len}]]]

RastToBitmap[im_, len_] := With[{ rect = Transpose[
        DeleteCases[Transpose[DeleteCases[
        Map[Mod[Round[Mean[#]] + 1, 2] &, ImageData[im], {2}],
        {0 ..}]], {0 ..}]]},   PadIm[rect, len, {len, len} - Dimensions[rect]]]

xuanxue = "玄学";
FullForm /@ Characters[xuanxue]
Image[RastToBitmap[Rasterize[#], 15], ImageSize -> 15*10] & /@ 
 Characters[xuanxue]
Image[RastToBitmap[Rasterize[Style[#, Large]], 18], 
   ImageSize -> 18*10] & /@ Characters[xuanxue]

square chars

Some other functions need also to be changed for variable sized input. Rather than posting essentially the same code again, I have attached a test notebook with a complete implementation. Meanwhile, output data files for frames of the animation above can be found at github. Nomenclature has changed, with introduction of revised "Trigram" function.

Maybe the hassle will be worth something though. Considering how many CJK ideographs there are, plus the crypto market's hyper+hyper++ trending rn, it could be possible soon to launch the richest set of NFTs ever minted, ha ha. Just imagine if we generalize Autoglyphs to 3D, then ReplacePart trigrams into one of $8$ corners. Hopefully block sizes are big enough, IDK just yet, lol. Then again, maybe not.

Attachments:
POSTED BY: Brad Klee
FullForm /@ Characters["玄学"]
Flatten[Outer[Rasterize[Style[#1, #2]] &, 
  Characters["玄学"], {{}, Small, Medium, Large}, 1]]
Partition[ImageDimensions /@ %, 4] // MatrixForm

sizes out

Notice, there are two distinct renders for each character, and changing style size changes padding. The inflexible function may need to be rewritten to identify bounding box, take contents, then pad with white space. Would be interesting to look at higher precision models. Slab size $128^3 \sim 10^6 vox$ seems like a reasonable bound for number of multiplications. For now, $9+2$ 2D characters used so far can be recovered by projecting from data found here.

Thanks for the compliment, happy holidays!

Enjoy year of the OX until February 1, 2021.

--Brad

POSTED BY: Brad Klee
Posted 2 years ago

Hi Brad,

Thanks for your kindly explanation.

My version still runs with some warnings, so I check it on Wolfram Cloud, It's same warning. I uploaded my notebook and Wolfram Cloud images. enter image description here

The diagonal finding is absolutely big progress for this puzzle. Congratulation! Character pixel like "---"," | “, works only in one direction, but fails in another. Projection to row and column must be full.So," / "or " \ " can works in both directions, then "C" or "S" that might be also works.

In my opinion, your project is 牛B (Super-cool) !

Attachments:
POSTED BY: Frederick Wu
Posted 2 years ago

Same issue for me on "12.3.1 for Mac OS X ARM (64-bit) (July 8, 2021)".

POSTED BY: Rohit Namjoshi

Hi Frederick, thanks for the thoughtful response.

I still can not run through your code, different Mathematica version ( I use 12.3 Chinese Version), but understand your RGB voxel puzzle better.

Curious if the problem is with rasterize or system fonts or something else. I double checked on the cloud and got a totally wacky output:

enter image description here

Magnified $10\times$ and "Antialiasing" is not an option for rasterize: "returns a rasterized version of the displayed form of expr". Suspicious behavior. Apple Safari seems to have the best display functions for CJK Unified Ideographs, see for example 道.

Otherwise, I think the code is okay when the input to function Partials is a set of $14 \times 14$ binary matrices. You suggest going smaller, but I would even consider going to $16 \times 16$ just to get a round power of two. The size of the space doesn't matter, only the size of the subspace containing non-zero values. For now leaving dimesnions as is, but later (especially when working with higher precision characters) it would be better to have adaptive sizing.

Your intuition is correct that the final theorem will involve conditionals, and I will now give a few counterexamples to show that dimensions of bounding box of non-zero subspace is an important concept in predicting output quality.

First, let me explain the math logic behind Partials, because it is kind of difficult to read in compact form. Say that we have three plane arrays of some square size, call them $Z,X,Y$, with letter indicating normal vector direction in a Cartesian $(x,y,z)$ system. Let each array contain a character pattern with binary values only, $1$ for presence (有), $0$ for absence (). The way to create a positive 3D ambigram is simply to multiply values and find a set, call it

$$A = \langle (i,j,k) \in \mathbb{Z}^3 : Z(i,j)X(j,k)Y(k,i) = 1 \rangle . $$

This would be good enough to calculate the monotone ABC ambigram from wikipedia. Indeed, the function Partials calculates $A$ first, but also calculates three supersets, which possibly intersect on $A$:

$$A_z = \langle (i,j,k) \in \mathbb{Z}^3 : X(j,k)Y(k,i) = 1 \rangle , \\ A_x = \langle (i,j,k) \in \mathbb{Z}^3 : Y(k,i)Z(i,j) = 1 \rangle , \\ A_y = \langle (i,j,k) \in \mathbb{Z}^3 : Z(i,j)X(j,k) = 1 \rangle . $$

Then it returns $A_z/A$, $A_x/A$, and $A_y/A$ as separate sets. In view outputs, elements of these sets get written to voxels in a simple $\{R,G,B\}$ color space. The complement action is important because, depending on how Graphics3D stacks coincident blocks, a wrong color could possibly occur (example follows).

To simplify the situation, we can set say $Y$ to monotone by choosing either $Y(k,i)=0$ or $Y(k,i)=1$ (over the entire domain) and see what happens.

xuanxue = "玄学";
bitmapBlank = Table[0, {i, 1, 14}, {j, 1, 14}];
bitmaps2 = With[{two = 
     RastToBitmap[Rasterize[#], 2] & /@ Characters[xuanxue]},
   Join[Flatten[Append[two, #] & /@ {bitmapBlank, bitmapBlank + 1}, 1],
    Flatten[Append[Reverse@two, #] & /@ {bitmapBlank, bitmapBlank + 1}, 1]]];
bitmaps2Ims = Image[Mod[# + 1, 2]] & /@ bitmaps2

chars

BWTest = imDat[Partials[Sequence @@ #]] & /@ Partition[bitmaps2, 3];
TableForm /@ BWTest
Flatten[Superpose /@ BWTest]
CheckVals = MapThread[ImageSubtract, 
{bitmaps2Ims, Flatten[Superpose /@ BWTest]}]

failed out

Great It worked! Except, oh wait a second, oh no!! The black check squares are hiding negative values that prove False results:

Partition[Mod[CheckVals, 2], 3]

corrected

So what happened? The bounding boxes of non-zero content do not match dimension from row to column, and this matters because they share an index diagonally. Notice that order of symbols matters, and for light on $Y$, the second character turns out wrong, while for dark on $Y$, both characters turn out fine.

Subtract @@ Reverse[MinMax[#]] & /@ 
 Transpose[Position[bitmaps2[[1]], 1]]
Subtract @@ Reverse[MinMax[#]] & /@ 
 MinMax /@ Transpose[Position[bitmaps2[[2]], 1]]
Out[] = {11,10}
Out[] = {11,10}        

The clipping actually isn't that bad, and we still get relatively readable outputs, even in three dimesions. It's interesting to see what happens choosing light or dark:

Column[RGBResults[#] & /@ Partition[bitmaps2, 3]]

test 1

test 2

test 3

test 4

To reiterate, monotone Blue voxelgrams are almost readable, while Dark Red / Green voxelgrams totally readable. Anyone can explain why one graph is all Blue, while the other no Blue?

Anyways, no we know the secret, so it leads to an exploit and "further testing". If we make the bounding box very small, then we can slice out tiny segments and make the crypto (ha ha, joke) more difficult to read.

bitmapX =   ReplacePart[
   bitmapBlank, {{7, 7} -> 1, {8, 8} -> 1, {8, 7} -> 1, {7, 8} -> 1,
    {6, 9} -> 1, {9, 6} -> 1, {6, 6} -> 1, {9, 9} -> 1}];
Exploit = {bitmaps2[[2]], bitmapX, bitmapBlank, bitmaps2[[2]], 
   bitmapBlank, bitmapX};
ExploitIm = Mod[Image[#] + 1, 2] & /@ Exploit

exploit test in

BWTest = imDat[Partials[Sequence @@ #]] & /@ Partition[Exploit, 3];
TableForm /@ BWTest;
Flatten[Superpose /@ BWTest]
Mod[MapThread[ImageSubtract, {ExploitIm, Flatten[Superpose /@ BWTest]}], 2]

exploit out

And in 3D:

test 5

test 6

These error cases are pretty convincing as to how a conditional could be written to gaurantee correct output. We need that sort of rigor before scheming about multi-million dollar autoglyph-style NFT business, possibly using some extra haskell programs. We definitely don't want multi-million dollar rounding errors!

Now the question of whether RGB face-first projection is equivalent to looking at Black and White Parts, Answer, seems yes, but maybe no. Say that we look along $z$ at either $Z(i,j)X(j,k)$ or $Y(k,i)Z(i,j)$. Chose constant $(i,j)=(a,b)$, if $Z(a,b)$ is set, then we should scan along variable $k$ and find some $Y(k,a)=1$ with $X(b,k)=0$ or some $X(b,k)=1$ with $Y(k,a)=0$, usually. A problem is if $X$ and $Y$ are transpose? Let's try one more test:

(at this point, functionalized test code needed?? )

bitmapSlash =   ReplacePart[
   Total[RotateRight[IdentityMatrix[14], #] & /@ {-1, 0, 1}], {{14, 
      1} -> 0, {1, 14} -> 0}];
Exploit2 = {Exploit[[1]], bitmapSlash, bitmapSlash};
Exploit2Im = Mod[Image[#] + 1, 2] & /@ Exploit2
BWTest = imDat[Partials[Sequence @@ #]] & /@ {Exploit2};
TableForm /@ BWTest
Flatten[Superpose /@ BWTest]
Mod[MapThread[ ImageSubtract, {Exploit2Im, Flatten[Superpose /@ BWTest]}], 2]
RGBResults[Exploit2]

slash learning

slash learning 3d

But ah ha! We also need $a=b$ chosen on diagonal, then the slash cut shows up, but it is in the Black / White data as desired. Whew, close call! Similarly if there is a natural absence, it should also occur in the Black / White data before RGB. What we can't predict easily is which correct color will show up where.

A few more counter cases, and the theorem will probably turn out clear as space.

POSTED BY: Brad Klee
Posted 2 years ago

Hi Brad,

I still can not run through your code, different Mathematica version ( I use 12.3 Chinese Version), but understand your RGB voxel puzzle better.

I assume, you want to find the minimal space for all 2D topology shape projection. Or we can raise questions like this, "What is the minimal space / rank of 3D cubic for all 2D topology shape projection. Does it exist? always exist? or depends on some conditions?"

I usually think from the simplest case, let's say 1X1X1 cubic, firstly. In our case (Black/White = voxel is True/False), if two faces/projections need Black, but the other face needs White, 1X1X1 cubic will fail. ​if three faces need all Black or White, 1X1X1 cubic will works.

In your case (RGB), if three faces needs is individually different RGB, 1X1X1 cubic will fail. if three faces needs is compatible,......., 1X1X1 cubic will works. I don't know exactly, how you define the relation between RGB with voxel True/False.

Then you think 2X2X2 cubic, it maybe rely on some conditions, like pixel position or topology shape, or some logical operations. Or you may apply previous 1X1X1 conclusion on 2X2X2 cubic case. then 3X3X3..etc.

Anther approach, you can shrink your cubic size, so far it is 14X14X14, you shrink it into 13X13X13 or even smaller, and see if you can still find a solution.

My feeling is the connection of 2D pixel image is key, if one face Black pixel is connected and its connection can be projected on its 1 dimension line boundary and cover the whole boundary, then 2 direction projections can certainly works. If two faces has this property, then 3 direction projection must work.

Anyway, I just talk some feeling to help, that is easy. If you want to make a mathematics proof, that is hard, and still a lot of work need to do. Good luck !

POSTED BY: Frederick Wu
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract