LoginSignup
2
2

【Mac/python3.8/English】Generate 100,000+ unique Kanji and sell them as NFTs Part-1

Last updated at Posted at 2022-02-16

Objectives

To generate unique Japanese Kanji that do not exist in the world and sell them on the OpenSea.
Most Japanese Kanjis are made of two to three radicals. The conviction of the radicals makes different Kanjis. For example, Japanese Kanji for "minute"「分」and "shell" 「貝」connived together and make「貧」, which means "poor" in Japanese. This project's goal is to generate random new Kanji that does not exist in the world and sell it as NFT.

If you already have NFTs to sell, Go to the automation part【Part 2】

Version

- python 3.8 
- Pillow 8.4.0

Get Radical Data

Thanks to Yuki Okuda for sharing the radical data on GitHub1. From these datasets, we can search radicals of a Kanji, and Kanji from its used radicals. For more information, take a look at OUT-OF-THE-BOX (Japanese).

Generate Unique Kanji

Since the datasets are 2 dictionary-shaped files that I can search for Kanjis that uses certain radicals and radicals that are used in Kanjis, I can generate unique Kanji by picking up the new combination of radicals. For better understanding, I used Kanjis that use only 2 radicals on top and bottom. Just like 「貧」. On OUT-OF-THE-BOX, it says there are 643 types of radicals and only 1,088 ways combinations of existing Kanji. So its permutation of 643 with 2 pairs, subtract with a number of existing Kanji (1088). There are 411,718 possible combinations of radicals.


 _{643}P_2 - 1088 = 411718


First, I load two dictionaries, one for picking the radicals and another for checking if the randomly generated Kanjis does not exist in the dictionary. I made a loop that continues until the program generates 100,000 unique Kanji. The code is below.


# Load radical dictionaries 
kanji2tb = load_json(".../kanjivg-radical-master/data/kanji2radical_top_buttom.json")
radi2kanji = load_json("/.../kanjivg-radical-master/data/radical2kanji.json")    


# Tb for Top and Bottom 
tb = getVal(kanji2tb)

# Create checking dictionary 
check = getValList(kanji2tb)

(I will explain ids and kaku dictionaries later on)

# Since the radicals are sorted by top, bottom, top, bottom... Separate radicals by index. 
btop = []
bbot = []

for i in range(len(tb)):
    if i%2==0:
        btop.append(tb[i])
    else: bbot.append(tb[i])

top = getList(Counter(btop))
bot = getList(Counter(bbot))


# Create empty lists for storing the Kanjis. 
c = 0
e = 0
words = []
ids = []


while c < 100000:
    word = [random.choice(top),random.choice(bot)]
    if word in check:
        e +=1
        pass
    elif word not in words:
        c += 1
        words.append(word)
        try:
            top_id = kaku[word[0]][2]
        except: 
            top_id = word[0]
        try:
            bot_id = kaku[word[1]][2]
        except:
            bot_id = word[1]
        ids.append(top_id+bot_id)
    else:
        pass


# Save Kanjis into dictionary as pickle. 

dic = {'ID': 'words'}

for i in range(len(ids)):
    dic[ids[i]] = words[i]

a_file = open('wordsID_1-1000000.pjl', "wb")
pickle.dump(dic, a_file)
a_file.close()

Balance out by the number of strokes

I would use pillow packages to generate images. Here are three simple steps.

  1. Generate two radical images with pillow
  2. Balance out both radicals by number of strokes
  3. Gather two images into one
  4. Repeat 1~3 100,000 times!

1. Generate Two Radical Images with Pillow

For the first step, I need to designate the font and colors. It is fun to choose random colors and fonts, but here, I'll just use basic back and white. For fonts, you can get them from your PC or there are a lot of free licensed fonts on the web. I recommend to use .ttc or .otf extension.


ttfontname = "/.../SOME_FONTS.ttc"

# white background
backgroundRGB = (255,255,255)

# black text color 
textRGB = (0,0,0)

canvasSize    = (1000, 1000)
    
img  = PIL.Image.new('RGB', canvasSize, backgroundRGB)
draw = PIL.ImageDraw.Draw(img)

fontsize = 1000

# Choose radicals top and bottom
top = word[0]
top = word[1]

#### Top radical ####
font = PIL.ImageFont.truetype(ttfontname, fontsize)
textWidth, textHeight = draw.textsize(top,font=font)

# Get center of the canvas and move it to bottom for better design 
center_down = (canvasSize[0]//2-textWidth//2, canvasSize[1]-textHeight) 
    
# Write 
font = PIL.ImageFont.truetype(ttfontname, fontsize)
textWidth, textHeight = draw.textsize(top,font=font)
draw.text(center_down, top, fill=textRGB, font=font)

# Save as png
img.save("top.png")
    
#### Bottom radical ####

# Get center of the canvas and move it to upper for better design 
textWidth, textHeight = draw.textsize(bot,font=font)
center_up = (canvasSize[0]//2-textWidth//2, -(canvasSize[1]-textHeight)

# Write 
font = PIL.ImageFont.truetype(ttfontname, fontsize)
textWidth, textHeight = draw.textsize(bot,font=font)
draw.text(center_up, bot, fill=textRGB, font=font)

# Save as png
img.save("bot.png")
    

2. Balance Out Both Radicals by Number of Strokes

By the code above, the Pillow generates the 2 same-size images. As you can see at 「分」「貝」, and「貧」, the radical「分」is squashed by「貝」. I have to balance these words so the generated Kanji looks like an actual Kanji. I used "kaku"; the number of strokes of the radical, to weight out the level of squishiness. If one radical has a small number of strokes (「分」in this case), the radical would be squished on the final copy. I tried several weights combinations to see if the generated Kanji looks like an actual Kanji. And below the code is my answer. What I have done here is get the ratio of strokes of top and bottom radicals. And multiplied them by 800. Since the font size needs to be around 1000 for a nice image.

Thank you for moji.or.jp for sharing the "kaku" for radicals here

# Install "kaku" data from mji00601_kaku.csv
with open('/.../mji00601_kaku.csv', mode='r') as infile:
    reader = csv.reader(infile)
    with open('coors_new.csv', mode='w') as outfile:
        writer = csv.writer(outfile)
        kaku = {rows[0]:rows[1:6] for rows in reader}
        
top_ratio = top_kaku/(top_kaku+bot_kaku)
bot_ratio = bot_kaku/(top_kaku+bot_kaku)
    
top_size = int(200+(top_ratio*800))
bot_size = int(200+(bot_ratio*800))

3. Gather Two Images into One

I defined get_concat_v_cut function from note.nkmk.me. Then run it with designated radical sizes by using resize function.

def get_concat_v_cut(im1, im2):
    dst = PIL.Image.new('RGB', (min(im1.width, im2.width), im1.height + im2.height))
    dst.paste(im1, (0, 0))
    dst.paste(im2, (0, im1.height))
    return dst

# Open images 
topimg = PIL.Image.open("top.png")
botimg = PIL.Image.open("bot.png")

#Fix the size
topimg = topimg.resize((1000, top_size))
botimg = botimg.resize((1000, bot_size))

#Gather and save
get_concat_v_cut(topimg, botimg).save( "/.../SOME_FILE/words.jpg")
   

4. Repeat 1~3 100,000 times!


def get_concat_v_cut(im1, im2):
    dst = PIL.Image.new('RGB', (min(im1.width, im2.width), im1.height + im2.height))
    dst.paste(im1, (0, 0))
    dst.paste(im2, (0, im1.height))
    return dst

with open('/.../mji00601_kaku.csv', mode='r') as infile:
    reader = csv.reader(infile)
    with open('coors_new.csv', mode='w') as outfile:
        writer = csv.writer(outfile)
        kaku = {rows[0]:rows[1:6] for rows in reader}

with open('wordsID_1-1000000.pjl', 'rb') as f:
    dic = pickle.load(f)

words = dic.values()
word = list(dic.values())

ttfontname = "/.../SOME_FONTS.ttc"

# white background
backgroundRGB = (255,255,255)

# black text color 
textRGB = (0,0,0)

# Setting the canvas
canvasSize = (1000, 1000)
img  = PIL.Image.new('RGB', canvasSize, backgroundRGB)
draw = PIL.ImageDraw.Draw(img)
fontsize = 1000

# Start number 
c = 1

# End number
end = 100000

for w in word:
    if c > end:
        break
    top = w[0]
    bot = w[1]
    
    # Get the top-bottom balance by strokes
    top_kaku = int(kaku[top][4])
    bot_kaku = int(kaku[bot][4])
    top_ratio = top_kaku/(top_kaku+bot_kaku)
    bot_ratio = bot_kaku/(top_kaku+bot_kaku)
    top_size = int(200+(top_ratio*800))
    bot_size = int(200+(bot_ratio*800))


    #### Top radical ####
    font = PIL.ImageFont.truetype(ttfontname, fontsize)
    textWidth, textHeight = draw.textsize(top,font=font)

    # Get center of the canvas and move it to bottom for better design 
    center_down = (canvasSize[0]//2-textWidth//2, canvasSize[1]-textHeight) 
    
    # Write 
    font = PIL.ImageFont.truetype(ttfontname, fontsize)
    textWidth, textHeight = draw.textsize(top,font=font)
    draw.text(center_down, top, fill=textRGB, font=font)

    # Save as png
    img.save("top.png")
    
    #### Bottom radical ####

    # Get center of the canvas and move it to upper for better design 
    textWidth, textHeight = draw.textsize(bot,font=font)
    center_up = (canvasSize[0]//2-textWidth//2, -(canvasSize[1]-textHeight)

    # Write 
    font = PIL.ImageFont.truetype(ttfontname, fontsize)
    textWidth, textHeight = draw.textsize(bot,font=font)
    draw.text(center_up, bot, fill=textRGB, font=font)

    # Save as png
    img.save("bot.png")
    
    # Draw 
    topimg = PIL.Image.open("top.png")
    botimg = PIL.Image.open("bot.png")
    topimg = topimg.resize((1000, top_size))
    botimg = botimg.resize((1000, bot_size))
    
    get_concat_v_cut(topimg, botimg).save( "/.../words/"+str(c)+".jpg")
    c +=1
    

Some examples

2289.jpg

If you change color and font

7323.jpg

Looking Good! Now, let's sell them on OpenSea!→【Part 2】

  1. All Kanjis are formatted by KanjiVG, so my projects are limited to this format.

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2