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

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

Hey gills, sorry for that! I got the server back online and you should be able to submit this awesome creation now. :slight_smile: I also confirmed that the Tampermonkey JS script is working too, you can see it in action e.g. on this page: kanji/起

I also published a new version of the Tampermonkey script that makes it work properly on the new review pages (I only updated the list of matching URLs, but I tested it and it works fine).

Please let me know if you need any help with anything at all, you can ping me on the server, I am Chiara#9001!

2 Likes

Brilliant, thanks so much for all of this!