Hicka text converter

Hicavan is a fictional character in The Legend of Zelda: Breath of the Wild. The symbols are found on all the Buildings of the Hica clan in Zelda. I thought they were decorative symbols until I saw the analysis of the Scientist and realized that they were words. Zelda is the best in the world!

Shika text and English characters to do mutual mapping, converter is to achieve the mutual conversion of two kinds of text, support to convert English characters into Shika text and shika text picture content parsing into English:

  • Tool demo address in this: kinglisky. Making. IO/zelda – words

  • Github. IO: nlush.com/zelda-words…

  • Warehouse address: github.com/kinglisky/z…

English -> Chicavan transformation

The characters of the fictional world are often created based on real characters, and the Shika script corresponds to the English alphabet in a one-to-one way, as follows:

Knowing the mapping relationship, we only need to convert each letter into the corresponding Hickaven. First, to prepare the text material of Hickaven, here is a recommended article: starting from the text of the fictional world.

The author has thoughtfully implemented a set of Hickaven fonts, which we can pull from the website:

3 type. Cn/CSS/fonts/s…

When we get the font file, we can configure @font-face to use it directly

@font-face {
  font-family: "SheikahGlyphs";
  src: url("https://3type.cn/css/fonts/sheikahglyphs-regular-webfont.woff2") format("woff2");
}

.sheikah-word {
    font-family: SheikahGlyphs;
}
Copy the code
<span class="sheikah-word">abc</span>
Copy the code

However, considering that we need to fix the size of the grid and spacing of the text later, we will use the font as an SVG icon in a different way.

  • Everythingfonts.com/woff2-to-sv…
  • icomoon.io/app

Using the above tools we get the single character SVG file of the font file and import it into iconFont to generate the font icon:

<script src="//at.alicdn.com/t/font_2375469_s4wmtifuqro.js"></script>
Copy the code

Then we wrap a simple file icon component:

<template>
    <svg
        class="word-icon"
        aria-hidden="true"
        :style="iconStyle"
    >
        <use v-if="iconName" :xlink:href="iconName" />
    </svg>
</template>

<script>
import { computed } from 'vue';

export default {
    name: 'WordIcon'.props: {
        // Icon name
        name: {
            type: String.required: true,},width: {
            type: Number.default: ' ',},height: {
            type: Number.default: ' ',},color: {
            type: String.default: ' ',},opacity: {
            type: String.default: ' ',}},setup: (props) = > {
        const iconName = computed(() = > props.name ? `#icon-${props.name}` : ' ');
        const iconStyle = computed(() = > ({
            color: props.color,
            opacity: props.opacity,
            width: `${props.width}px`.height: `${props.height}px`,}));return{ iconName, iconStyle, }; }};</script>

<style>
.word-icon {
    overflow: hidden;
    width: 1em;
    height: 1em;
    padding: 0;
    margin: 0;
    fill: currentColor;
}
</style>
Copy the code

The English character translation panel can be easily implemented by using the newline character \n to split text groups and replace unsupported characters with null characters:

<template>
    <section
        class="words-panel"
        ref="container"
    >
        <div
            class="words-panel__groups"
            v-for="(words, index) in wordGroups"
            :key="index"
        >
            <WordIcon
                class="words-panel__icon"
                v-for="(word, idx) in words"
                :key="idx"
                :name="word"
                :width="size"
                :height="size"
            >
                {{ word }}
            </WordIcon>
        </div>
    </section>
</template>

<script>
import { computed, ref } from 'vue';
import WordIcon from './icon.vue';

const WORDS = [
    '0'.'1'.'2'.'3'.'4'.'5'.'6'.'7'.'8'.'9'.'a'.'b'.'c'.'d'.'e'.'f'.'g'.'h'.'i'.'j'.'k'.'l'.'m'.'n'.'o'.'p'.'q'.'r'.'s'.'t'.'u'.'v'.'w'.'x'.'y'.'z'.'. '.'! '.'? '.The '-',];export default {
    name: 'WordsPanel'.components: {
        WordIcon,
    },

    data() {
        return {
            words: 'hello world'.size: 60}; },setup: (props) = > {
        const container = ref(null);
        const wordGroups = computed(() = > {
            return props.words
                .toLowerCase()
                .split('\n')
                .map(words= > words.split(' ').map(v= > WORDS.includes(v) ? v : ' '));
        });

        return{ container, wordGroups, }; }};</script>
Copy the code

The last step is to export the Shika text, because we do not involve the complex DOM structure and style, here we are lazy to directly export the text panel of the target to a picture by using the way of front-end DOM drawing. There are many ready-made libraries, such as HTML2Canvas and DOM-to-image. Here we use dom-to-image to generate the image. The library is very small and easy to use.

Here’s how DOM rendering works. DOM rendering uses the foreignObject tag of SVG elements. We can insert a custom HTML fragment under the foreignObject tag and draw the entire SVG as an image onto the canvas:

(async function () {
    const svg =
    `
       
        
        
OUTPUT
`
; const dataUrl = 'data:image/svg+xml; charset=utf-8,' + svg; const loadImage = (url) = > { return new Promise((resolve, reject) = > { const img = new Image(); img.onload = () = > resolve(img); img.onerror = (e) = > reject(e); img.src = url; }); }; const image = await loadImage(dataUrl); const canvas = document.createElement('canvas'); canvas.width = 120; canvas.height = 60; const ctx = canvas.getContext('2d'); ctx.drawImage(image, 0.0); console.log(canvas.toDataURL()); }) ();Copy the code

The internal processing of dom-to-image is similar to the above process, and our font icon will be processed to generate an SVG structure similar to the following:

<svg xmlns="http://www.w3.org/2000/svg">
	<foreignObject width="120" height="60">
    		<svg>
            		<use xlink:href="#icon-a" />
        	</svg>
	</foreignObject>
</svg>
Copy the code

The special use label used in the icon, the content referenced by use exists globally, so we need to deal with this part of the symbol reference when drawing the image.

Processed into the following structure:

<svg xmlns="http://www.w3.org/2000/svg">
	<foreignObject width="120" height="60">
    		<svg>
      			<symbol id="icon-a" viewBox="0 0 1024 1024">
        			<path d="xxx"></path>
      			</symbol>
      			<use xlink:href="#icon-a" />
    		</svg>
   	</foreignObject>
</svg>
Copy the code

The final image export can be processed as follows:

import domtoimage from 'dom-to-image';

// SVG icon dependency in the fix node
function fixSvgIconNode(node) {
    if (node instanceof SVGElement) {
        const useNodes = Array.from(node.querySelectorAll('use') | | []); useNodes.forEach((use) = > {
            const id = use.getAttribute('xlink:href');
            // Insert the 
      
        node that depends on an SVG image under the current SVG node
      
            if(id && ! node.querySelector(id)) {const symbolNode = document.querySelector(id);
                if (symbolNode) {
                    node.insertBefore(
                        symbolNode.cloneNode(true),
                        node.children[0]); }}}); }return true;
}

export default function exportImage (node) {
    return domtoimage.toPng(node, { filter: fixSvgIconNode })
        .then(dataUrl= > {
            console.log(dataUrl);
        });
}
Copy the code

Since then, the conversion from English to Shika has been completed, focusing on how to realize the translation of shika card content.

Shikavan -> English translation

The content of the final output is a picture, we need to consider how to “translate” the content of the picture, here I made a quotation, may we don’t need real resolution translation out of the content of the picture, or we can consider a skillful way to original text information hiding in the final image?

Let’s try a clever way to hide the original text message in the picture.

Image blind watermarking

The technology of hiding the target information in the image without affecting the visual display of the image can be called image steganography. If the hidden target object is an image, it can be called blind watermarking. Blind watermarking is often used for image copyright protection and image leak tracking.

Let’s start with the simplest steganography for images: LSB (Least Significant Bit).

We all know that every pixel of an image is mixed with the color of RGB channels, and the color value of RGB channel +1 or -1 cannot be distinguished by the naked eye, just take RGB (0, 0, 0) and RGB (1, 0, 0), can you distinguish it by the naked eye?

Obviously not, so we can increase or decrease the color value by 1 in a RGB channel to make it odd (corresponding to 1) or even (corresponding to 0), we only need to transform the hidden information into binary can be mapped to the odd and even value of a color channel can realize the information hiding. The process of parsing is also very simple, read the color value on the target channel, odd number is 1, even number is 0, invert the binary data of 01 and then restore the original data.

The message for the hickavin above is Hello World, which we are now trying to hide in the image above.

To convert Hello World to binary, we can convert each letter to the corresponding ASCII code and then to the corresponding 8-bit binary number, but since we are hiding information to encode text, why not use some existing tools (which is actually a bit of work), two-dimensional code is a good carrier.

We can generate a black and white QR code corresponding to Hello World:

The information hiding is the qr code below to Calvin results in the picture, because it is black and white pictures of qr code, we can simply ascribe black pixel value is 0 (even) white pixel value as 1 (odd), but because of qr code image and Calvin the size of the picture is not consistent, we are not convenient to two images of the pixels in a one to one correspondence, I can first adjust the size of the TWO-DIMENSIONAL code to be consistent with the Picture of Hicharvan.

With the image ready, let’s first implement methods to hide and parse the watermark:

// Write the QR code watermark
function writeMetaInfo(baseImageData, qrcodeImageData) {
    const { width, height, data } = qrcodeImageData;
    for (let x = 0; x < width; x++) {
        for (let y = 0; y < height; y++) {
            // Select r channel to hide information
            const r = (x + y * width) * 4;
            const v = data[r];
            // The white part (background) of the QR code is marked as 1, and the black part (content) is marked as 0
            const bit = v === 255 ? 1 : 0;
            // If the current R channel color values are inconsistent with the corresponding pixels of the TWO-DIMENSIONAL code, add or subtract one to make the parity consistent
            if (baseImageData.data[r] % 2! == bit) { baseImageData.data[r] += bit ?1 : -1; }}}return baseImageData;
}

// Read the qr code watermark
function readMetaInfo(imageData) {
    const { width, height, data } = imageData;
    const qrcodeImageData = new ImageData(width, height);
    for (let x = 0; x < width; x++) {
        for (let y = 0; y < height; y++) {
            // Read the r channel message
            const r = (x + y * width) * 4;
            // The odd color is white 255, and the even color is black 0
            const v = data[r] % 2= = =0 ? 0 : 255;
            qrcodeImageData.data[r] = v;
            qrcodeImageData.data[r + 1] = v;
            qrcodeImageData.data[r + 2] = v;
            qrcodeImageData.data[r + 3] = 255; }}return qrcodeImageData;
}
Copy the code

The complete example is here, below is the Hidden QR code after the Hicaven picture, is not the naked eye can not see what changes?

The two-dimensional code parsed from the corresponding card is as follows. Although it has some noise information, it does not affect the recognition of the two-dimensional code.

The earliest version of the Hika image recognition is to use the image of the least significant bit to hide information, when the completion of the jubilant ready to share to wechat so that xiaoke love, etc.! Vaguely remember wechat will compress the picture, whether to send wechat and then download down to try?

Heaven! Sure enough, after sharing through wechat, the images will be compressed (wechat will process all PNG images into JPG images), resulting in the loss of odd and even bits of information hidden in the images. After trying to analyze the compressed images shared by wechat, the images are finally obtained as follows:

The realization of the least significant bit is simple, but the anti-jamming ability of the hidden information is poor, and the image compression is very easy to cause the loss of the parity bit information. We need to consider how to improve the anti-jamming ability of hidden information. The least effective way is to hide information in a pixel channel. What if we can expand the scope of hidden information? Two-dimensional code, for example, uses black and white color blocks to identify data bits. Can we hide information through blocks of color? Here’s an example:

What does that color block mean?

[100.200]
[01100100.11001000]
Copy the code

In fact, 16 black and white blocks represent the numbers 100 and 200, black represents 0 and white represents 1. Parsing is also very simple, the above picture is divided into 16 blocks, check each block, black reads 0 and white reads 1. I’m going to use black and white to map the 01 up here and I’m going to use a different rule, let’s say RGB 0, 0, 0 is 0, RGB 2, 2 is 1. What does the resulting image look like?

Is it difficult to see the color difference with the naked eye, but we do hide the information in the picture, the parsing rules are very simple, split the color block to read the color, RGB (0, 0, 0) is 0, greater than RGB (0, 0, 0) is 1, so that to some extent as long as the picture is not too compressed, we can still parse the original information, Of course, increasing the contrast between the two colors is also a method (invisible to the naked eye as high as possible).

The simple implementation is as follows:

// Unified into 8 bits
function paddingLfet(bits) {
    return ('00000000' + bits).slice(-8);
}

function write(data) {
    const bits = data.reduce((s, it) = > s + paddingLfet(it.toString(2)), ' ');
    const size = 100;
    const width = size * bits.length;
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = size;
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '# 0000000';
    ctx.fillRect(0.0, width, size);
    for (let i = 0; i < bits.length; i++) {
        if (Number(bits[i])) {
            ctx.fillStyle = '# 020202';
            ctx.fillRect(i * size, 0, size, size); }}return canvas.toDataURL();
}

async function read(url) {
    const image = await loadImage(url);
    const canvas = document.createElement('canvas');
    canvas.width = image.naturalWidth;
    canvas.height = image.naturalHeight;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0.0);
    const size = 100;
    const bits = [];
    for (let i = 0; i < 16; i++) {
        const imageData = ctx.getImageData(i * size, 0, size, size);
        const r = imageData.data[0];
        const g = imageData.data[1];
        const b = imageData.data[2];
        bits.push(r + g + b === 0 ? 0 : 1);
    }
    return bits;
}
Copy the code

The full example is here

This method is quite stable, but it can hide too little information, we used to hide a lot of text information is not practical, but can hide some key information, later we will use this way to record the size of the grid of The Card, used for picture analysis.

Images of the hidden watermark is a profound knowledge, this is just some simple implementation, a practical production version is generated by the Fourier transform image frequency domain graph, and then the watermark information hiding in the frequency domain graph do Fourier inverse transformation back into normal images, so the generated image has good anti-interference ability, but the super classes fixed (eating), touch clear to try again.

Recognition of similar images

As for the above problem, since hiding text message “clever” scheme doesn’t work, let’s try to actually parse the content of the image

Image recognition of the text can think of technology is OCR, but also pulled to the existing tool TesseractJS, but want to achieve a set of shika text recognition needs to train the generation of Raineddata shika text, here we try to use a simple way to achieve (mainly too dish not to play 😂).

To generate pictures of card, we already know the mapping relationship symbols and English letters, and they are generated by the same set of fonts, text in the same grid size configuration in the picture, space son said an empty string, if we can split the text a grid, with known character image matching, Pick the character that is closest to the image and the image is not the recognition code of the text content? The core content here is actually how to realize the recognition of two similar pictures.

Let’s start with two key pieces of information:

  • The size of the grid
  • Standard Shica symbol dictionary picture

We have to know the size of the grid in order to split a Shica picture, right? In fact, we have secretly hidden this information in the generated image, I posted a picture for you to understand:

Remember the way we used color blocks above to hide information? When generating the Sika image we hid some hidden information in the first line, but the color used was so close to the background that it was hard to distinguish it from the naked eye.

In the first line of the image, we inserted three key information: the text arrangement (0 or 1 identification), the size of the text grid, and the width of the image (several grid sizes). Each information binary is 8 bits long, and the total length is 24 bits. When we get the image for parsing, we just need to take the first line of the image (2 ~ 4 pixels high enough) and split it into 24 equal pieces. The color of the first one is used to identify 0 (because the first eight bits indicate that the text is only 01 in both cases, and the first seven bits of the binary are 0 instead of 8 bits). The remaining 23 color blocks whose color is the same as the first one are marked as 0, if they are not used, they are marked as 1, simple violence.

Through the above method, we can get the most critical information about the grid size of the picture. We can divide the picture into equal grids according to the grid size:

We end up with a grid like this, and now all we need to do is match the symbol from the dictionary image that we prepared in advance, which is this one:

The grid size of 100 pictures, from left to right are: abcdefghijklmnopqrstuvwxyz0123456789. -! ? , we can also split the symbol picture corresponding to each letter in order.

The rest is to compare the similarity of two pictures and find the most similar picture from the dictionary pictures. The recognition principle of similar pictures is actually very simple, and there is a very detailed arrangement of an article here will not be repeated.

All of the following similar image check content involved in the simple implementation of similar image recognition, interested students can have a look.

Just to summarize the principle of comparing similar images, we can’t compare two images directly. What if they are both represented in binary? If two images can both become binary strings of the same length as below, we can compare the similarity of the two images by judging the number of differences (Hamming distance) at the same position of the two characters. The smaller the difference, the more similar the images will be.

00101101, 00111001,Copy the code

The processing steps are as follows:

  • Shrink the picture down to 8 x 8
  • Grayscale processing of the picture
  • Binary processing of the picture (unified into black and white can be mapped to 01)
  • Output picture fingerprint

The tool is here, and the picture is uniformly reduced to 64×64. The fingerprint of the 8×8 picture is as follows:

What we need to do is to generate the picture fingerprint corresponding to 40 English characters (8×8 size), and then generate the corresponding picture fingerprint (8×8 size) corresponding to the split grid of the parsed picture according to the same process. Then, as long as the fingerprint with the smallest Hamming distance is matched from the dictionary, the original English characters will be matched. Then print the text in the same order as the text.

Specific code implementation of the code is more than posted here, interested students can click here to see the code implementation.

other

  • Simple implementation of similar image recognition
  • Unspeakable secret – Picture steganography that can be played on the front end
  • Blind watermarking and picture steganography
  • How do you understand the Formula for the Fourier transform?
  • Start with the words of the fictional world
  • The Legend of Zelda
  • Gamer’s Romance – Teach you to write a love letter in Shika script
  • sheikah-language
  • Sheikah_Language_Translations

The warehouse is built for a long time, then played zelda is ecstasy, trying to make the Calvin word play card generator, then put two years of goo goo goo, found New Year starts when fishing in finishing warehouse, just want to try and vite vue3, so fishing wrote a small tool, flipped through the warehouse found there are too many things yourself aside, I hope you’re still curious about the world

Finally to the special you ~