New volunteer project: We're using AI to create mnemonic images for every radical, kanji, vocabulary. Come join us!

A few weeks ago, Mo-Kanji posted about using Midjourney to generate mnemonic images for WaniKani using AI. We’re taking this concept to the next level with a volunteer project where we plan to use AI to generate images for every radical, kanji, and vocabulary on WaniKani.

Install: Userscript to view mnemonic images

This script embeds them in WK pages, like this:


Our mnemonic image embedded by our userscript into the page for 業 (Business). I made this one with DALL-E 2.


We do separate mnemonic images for meanings and for readings. This one was made in Midjourney by saraqael, who also created the submission bot.

Discord server

Even with the power of AI on our side, to cover the 11,619 items on WaniKani, we will need help! This is where you come in. Join our Discord server:

You submit your creations on Discord using the /submit bot command, and they are stored in our database and immediately appear on the WaniKani website for those using our userscript. If multiple users submit creations for the same item, we vote on which one we like best.

What if I don’t have access to an AI image generator?

Everyone is welcome! You can use free AI image generators like crayon.ai, or you can draw your own - it doesn’t matter if it’s bad art as long as it’s memorable! Plus, you can watch the cool mnemonic images as they come in and help us vote on the best ones.


Images can be hand-drawn (like AmandaBear’s images), edited in Photoshop, combined into multiple-image comic strips showing a sequence of events, or you can invent and superimpose your own mnemonic text. We’re open to anything, and we vote on which ideas we like the best.

Is WaniKani going to add these to the official website?
We hope someday! But we think we have to prove out their value first. Try the userscript, join our Discord, and give us feedback. We’ll be excited to welcome you!

31 Likes

@Mods something for you? :slight_smile:

3 Likes

Wooo, it’s here!

2 Likes

Not sure if you were looking for some type of input but thanks for informing us about the project!

6 Likes

We don’t need any kind of input from mods, we’re just here to announce and recruit. :slight_smile: But thank you for your response!

7 Likes

Thank you for this cool project, I’ve just tried it and it seems not many items are covered so far? What are the current stats for Kanji?

2 Likes

So far we’re at 91 submissions covering 27 items and another 37 half-done (0.71%), we’re just getting started. I’m not sure how many are kanji. We are importing the ones from the earlier thread and are thinking of importing AmandaBear’s art which will help a lot but we need to get in touch with her and make sure we have permission (and still sorting out how to do that). As we get more and more people contributing though I’m optimistic we’ll make faster progress. :slight_smile:

4 Likes

Did you guys take pictures down from the original thread from the script? I remember a few of my pictures were used, but now they’re no longer there.

2 Likes

We had to reset when we rebuilt the scripts and moved the database, but we’re in the process of re-adding the ones from the original thread. Don’t worry, we definitely want to use all of those. :slight_smile:

4 Likes

It’s incredible how quickly AI-generated art has progressed. I first remember seeing it roughly a year or so ago and the images were barely legible. Now I have to look closely to see the “computer prints” (probably a better pun there).

Anyway, super cool idea. Please feel free to post more notable examples!

9 Likes

I got access to Midjourney today and also joined the discord :smiley:
Looking forward to adding some pictures. I tried the ai already and it’s amazing!

1 Like

This is how Skynet was born.

2 Likes

These are hilarious!
499-thumb

@Sinyaven the script you made firs\t is not working any more:

Is related at the the script depandcy WK Item Info Injector

Could you check how to get this working again. I have checked the script topics but could not find your version.

1 Like

@saraqael has based this script on my Mnemonic Artwork script. Comparing the two scripts, the following version should make it work with the new pages:

// ==UserScript==
// @name         WaniKani AI Mnemonic Images
// @namespace    aimnemonicimages
// @version      1.6
// @description  Adds AI images to radical, kanji, and vocabulary mnemonics.
// @author       Sinyaven (modified by saraqael)
// @license      MIT-0
// @match        https://www.wanikani.com/*
// @match        https://preview.wanikani.com/*
// @require      https://greasyfork.org/scripts/430565-wanikani-item-info-injector/code/WaniKani%20Item%20Info%20Injector.user.js?version=1166918
// @homepageURL  https://community.wanikani.com/t/new-volunteer-project-were-using-ai-to-create-mnemonic-images-for-every-radical-kanji-vocabulary-come-join-us/58234
// @grant        none
// ==/UserScript==

(async function () {
	"use strict";
	/* global wkItemInfo */
	/* eslint no-multi-spaces: "off" */

	//////////////
	// settings //
	//////////////

	const ENABLE_RESIZE_BY_DRAGGING = true;
	const USE_THUMBNAIL_FOR_REVIEWS = true;
	const USE_THUMBNAIL_FOR_ITEMINF = false;

	//////////////

	if (!localStorage.getItem("AImnemonicMaxSize")) localStorage.setItem("AImnemonicMaxSize", 400); // standard size
	const folderNames = {
		r: 'Radicals',
		k: 'Kanji',
		v: 'Vocabulary',
	}

	function getUrl(wkId, type, mnemonic, thumb = false) {
		return 'https://wk-mnemonic-images.b-cdn.net/' + type + '/' + mnemonic + '/' + wkId + (thumb ? '-thumb.jpg' : '.png');
	}

	function init() {
		wkItemInfo.forType("radical,kanji,vocabulary").under("meaning").append("Meaning Mnemonic Image", ({ id, type, on }) => artworkSection(id, type, 'Meaning', on));
		wkItemInfo.forType("radical,kanji,vocabulary").under("reading").append("Reading Mnemonic Image", ({ id, type, on }) => artworkSection(id, type, 'Reading', on));
	}

	async function artworkSection(subjectId, type, mnemonic, page) {
		const fullType = folderNames[type[0].toLowerCase()];
		const isItemInfo = page === 'itemPage';
		const useThumbnail = isItemInfo ? USE_THUMBNAIL_FOR_ITEMINF : USE_THUMBNAIL_FOR_REVIEWS;

		const imageUrl = getUrl(subjectId, fullType, mnemonic, useThumbnail); // get url (thumbnail in reviews and lessons)

		const image = document.createElement("img"); // image loading
		if (!(await new Promise(res => {
			image.onload = () => res(true);
			image.onerror = () => res(false);
			image.src = imageUrl;
		}))) return null;

		if (ENABLE_RESIZE_BY_DRAGGING) {
			const currentMax = parseInt(localStorage.getItem("AImnemonicMaxSize")) || 900;
			makeMaxResizable(image, currentMax).afterResize(m => { localStorage.setItem("AImnemonicMaxSize", m); let e = new Event("storage"); e.key = "AImnemonicMaxSize"; e.newValue = m; dispatchEvent(e); });
			addEventListener("storage", e => { if (e.key === "AImnemonicMaxSize") { image.style.maxWidth = `min(${e.newValue}px, 100%)`; image.style.maxHeight = e.newValue + "px"; } });
		}
		return image;
	}

	function makeMaxResizable(element, currentMax, lowerBound = 200) {
		let size = 0;
		let max = currentMax;
		let oldMax = currentMax;
		let callback = () => { };
		let pointers = [{ id: NaN, x: 0, y: 0 }]; // image origin is always a pointer (scaling center)

		function getDistanceSum(e) {
			removePointer(e);
			addPointer(e);
			function length(p1, p2) { let d = [p1.x - p2.x, p1.y - p2.y]; return Math.sqrt(d[0] * d[0] + d[1] * d[1]); }
			return pointers.reduce((total, p1) => pointers.reduce((l, p2) => l + length(p1, p2), total), 0);
			//return pointers.reduce(([len, lastP], p) => [len + length(lastP, p), p], [0, pointers[pointers.length - 1]])[0]; // old version using circumference - order dependent! => not usable if pointers.length > 3
		};
		function removePointer(e) {
			if (e) pointers = pointers.filter(p => p.id !== e.pointerId);
		}
		function addPointer(e) {
			if (!e) return;
			let rect = element.getBoundingClientRect();
			pointers.push({ id: e.pointerId, x: e.clientX - rect.left, y: e.clientY - rect.top });
		}
		function startResizing(e) {
			if (e.button !== 0) return;

			if (pointers.length < 2) {
				max = parseFloat(element.style.maxHeight);
				oldMax = max;
			}

			size = getDistanceSum(e);
			element.addEventListener("pointermove", doResizing);
			element.addEventListener("pointerup", endResizing);
			element.addEventListener("pointercancel", cancelResizing);
			element.setPointerCapture(e.pointerId);
			e.preventDefault();
		}
		function doResizing(e) {
			if (!(e.buttons & 1)) return;

			let newSize = getDistanceSum(e);
			max *= newSize / size;
			size = newSize;
			updateMax();
		};
		function endResizing(e) {
			doResizing(e);
			max = Math.min(max, element.parentElement.clientWidth, element.naturalWidth);
			oldMax = Math.max(max, lowerBound);
			cancelResizing(e);
			callback(max);
		}
		function cancelResizing(e) {
			removePointer(e);
			size = getDistanceSum();
			if (pointers.length > 1) return;

			max = oldMax;
			updateMax();
			element.removeEventListener("pointermove", doResizing);
			element.removeEventListener("pointerup", endResizing);
			element.removeEventListener("pointercancel", cancelResizing);
			element.releasePointerCapture(e.pointerId);
		}
		function updateMax() {
			let m = Math.max(max, lowerBound);
			element.style.maxWidth = `min(${m}px, 100%)`;
			element.style.maxHeight = m + "px";
		};
		updateMax();
		element.style.touchAction = "pan-x pan-y";
		element.addEventListener("pointerdown", startResizing);

		return { afterResize: f => { callback = f; } };
	}

	init();
})();
1 Like

Thank you for updating the script I forked from yours! You’re really fast with this. I’m going to update it on the download page as well. :smiling_face:

2 Likes

Your own version works, but this new one does not.
Strange. Is it because the github key change perhaps?

For me, the code that I posted above works. If it doesn’t work for you, can you go through the steps in this guide and provide more info about what doesn’t work?

1 Like

The problem lies in the fact that not every item has an image.
So your script works as stated by you.

Seems like this project might be a bit dead? Trying to make new submissions on the Discord server doesn’t seem to function anymore. Anyway, here’s a 魚 / さかな / soccer nun fish:

3 Likes