Skip to content

Commit

Permalink
start working on similar skins (dhash)
Browse files Browse the repository at this point in the history
  • Loading branch information
KaiAF committed Feb 28, 2024
1 parent ce0c939 commit fa84d77
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 28 deletions.
17 changes: 17 additions & 0 deletions public/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,17 @@ video {
max-width: 1536px;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.pointer-events-none {
pointer-events: none;
}
Expand Down Expand Up @@ -558,6 +569,9 @@ video {
.order-2 {
order: 2;
}
.float-right {
float: right;
}
.m-2 {
margin: 0.5rem;
}
Expand Down Expand Up @@ -718,6 +732,9 @@ video {
.w-48 {
width: 12rem;
}
.w-5 {
width: 1.25rem;
}
.w-6 {
width: 1.5rem;
}
Expand Down
4 changes: 3 additions & 1 deletion schemas/skins.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ CREATE TABLE IF NOT EXISTS `skins` (
`model` tinyint(1) NOT NULL DEFAULT '0',
`userCount` int NOT NULL DEFAULT '0',
`hash` char(32) NOT NULL,
`dhash` char(16) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `hash` (`hash`),
KEY `skinId` (`skinId`),
KEY `createdAt` (`createdAt`),
KEY `url` (`url`)
KEY `url` (`url`),
KEY `dhash` (`dhash`)
);
2 changes: 1 addition & 1 deletion src/front-end/partials/footer.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div class="text-sm flex">
<p>© 2021 - 2024, Iris</p>
<p class="ml-[15px]">
master@<a id="github_hash" class="text-indigo-600 dark:text-indigo-500 hover:underline" href="https://github.com/livzmc/website/commits/8c499d85ac1710a2ded5dc3788494677d9b83595" target="__">8c499d8</a>
master@<a id="github_hash" class="text-indigo-600 dark:text-indigo-500 hover:underline" href="https://github.com/livzmc/website/commits/ce0c9395f276851c448db3bf2721f09d0966bff5" target="__">ce0c939</a>
</p>
</div>
</div>
Expand Down
11 changes: 6 additions & 5 deletions src/front-end/partials/pagination.ejs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<%
if (typeof hasNextPage === 'undefined') hasNextPage = true;
if (typeof options != 'undefined') {
if (options && options.length > 0) {
options = `&${options.join('&')}`;
Expand All @@ -17,11 +18,11 @@
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</a>
<a class="dark:bg-gray-900 dark:border-gray-700 z-10 bg-indigo-50 border-indigo-500 text-indigo-600 relative inline-flex items-center px-4 py-2 border text-sm font-medium" href="<%= path %>?page=<%= parseInt(page) %><%= options %>"><%= parseInt(page) %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium" href="<%= path %>?page=<%= parseInt(page) + 1 %><%= options %>"><%= parseInt(page) + 1 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium" href="<%= path %>?page=<%= parseInt(page) + 2 %><%= options %>"><%= parseInt(page) + 2 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 hidden md:inline-flex relative items-center px-4 py-2 border text-sm font-medium" href="<%= path %>?page=<%= parseInt(page) + 3 %><%= options %>"><%= parseInt(page) + 3 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" href="<%= path %>?page=<%= parseInt(page) + 1 %><%= options %>">
<a class="dark:bg-gray-900 dark:border-gray-700 z-10 bg-indigo-50 border-indigo-500 text-indigo-600 relative inline-flex items-center px-4 py-2 border text-sm font-medium" <%= !hasNextPage ? 'disalbed=""' : '' %>href="<%= path %>?page=<%= parseInt(page) %><%= options %>"><%= parseInt(page) %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium" <%= !hasNextPage ? 'disalbed=""' : '' %>href="<%= path %>?page=<%= parseInt(page) + 1 %><%= options %>"><%= parseInt(page) + 1 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium" <%= !hasNextPage ? 'disalbed=""' : '' %>href="<%= path %>?page=<%= parseInt(page) + 2 %><%= options %>"><%= parseInt(page) + 2 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 bg-white border-gray-300 text-gray-500 hover:bg-gray-50 hidden md:inline-flex relative items-center px-4 py-2 border text-sm font-medium" <%= !hasNextPage ? 'disalbed=""' : '' %>href="<%= path %>?page=<%= parseInt(page) + 3 %><%= options %>"><%= parseInt(page) + 3 %></a>
<a class="dark:bg-gray-900 dark:border-gray-700 relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" <%= !hasNextPage ? 'disalbed=""' : '' %>href="<%= path %>?page=<%= parseInt(page) + 1 %><%= options %>">
<span class="sr-only">Next</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
Expand Down
25 changes: 23 additions & 2 deletions src/front-end/skins/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
CLASS: "px-4 rounded bg-gray-100 dark:bg-gray-900 ml-[5px] py-1 font-semibold border-2 border-indigo-600/50 dark:border-indigo-500/50 hover:underline",
ACTIVECLASS: "border-indigo-600/100 dark:border-indigo-500/100",
}
if (typeof dh === 'undefined') dh = null;
%>
<!DOCTYPE html>
<html class="<%= theme %>" lang="en">
Expand Down Expand Up @@ -31,9 +33,28 @@
</div>
</div>
</div>
<% if (dh) { %>
<%-
await include('../partials/pagination', {
path: '/skins/similar',
options: [`dh=${dh}`],
page: number,
hasNextPage,
});
%>
<% } else if (!random) { %>
<%-
await include('../partials/pagination', {
path: '/skins/new',
options: null,
page: number,
});
%>
<% } %>
<div class="py-1"></div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-1">
<div>
<% if (true) { // ads %>
<% if (ads) { // ads %>
<div class="dark:bg-gray-600 bg-gray-200 p-2 m-2 rounded">
<div class="text-center">
<p class="uppercase">Advertisement</p>
Expand Down Expand Up @@ -72,7 +93,7 @@
</div>
</div>
<div>
<% if (true) { // ads %>
<% if (ads) { // ads %>
<div class="dark:bg-gray-600 bg-gray-200 p-2 m-2 rounded">
<div class="text-center">
<p class="uppercase">Advertisement</p>
Expand Down
54 changes: 53 additions & 1 deletion src/front-end/skins/skin.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,33 @@
</div>
</div>
</div>
<div class="mt-[15px]"></div>
<div id="similar-skins" class="mx-auto">
<% if (similarSkins.length > 0) { %>
<div class="w-1/2 mx-auto sm:px-6 lg:px-8">
<h1 class="text-2xl font-semibold">
Similar Skins
<% if (similarSkins.length > 18) { %>
<span class="float-right"><a class="bg-indigo-500 p-1 rounded hover:underline" href="/skins/similar?dh=<%= skin.dhash.toString().slice(0, 8) %>">Show More</a></span>
<% } %>
</h1>
<div class="mt-[5px]"></div>
<hr>
<div class="mt-[5px]"></div>
<div class="flex flex-wrap justify-center items-center gap-1">
<% for (let i = 0; i < similarSkins.length && i < 18; i++) { %>
<% const skin = similarSkins[i]; %>
<a href="/skins/<%= skin.skinId %>">
<img draggable="false" width="88" height="188" class="md:rounded-none rounded-lg mx-auto md:py-0 py-1" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAAC8CAYAAAD8QGmgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABXSURBVHhe7cExAQAAAMKg9U9tB28gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE41A0sAAfNFXjMAAAAASUVORK5CYII=" alt="<%= skin.skinId %>" skin="<%= skin.skinId %>">
</a>
<% } %>
</div>
<div class="mt-[5px]">
<small class="text-xs">Diff Hash: <%= skin.dhash %></small>
</div>
</div>
<% } %>
</div>
</main>
<%-
await include('../partials/footer', {
Expand All @@ -97,8 +124,33 @@
await include('../partials/skinviewer', {
skin: `${textureServer}/skins/${skin.skinId}.png`,
cape: null,
slim: users[users.length - 1].model === 1,
slim: users[users.length - 1]?.model === 1,
});
%>
<script>
const skinViewerImage = new skinview3d.SkinViewer({
width: 88,
height: 188,
cameraX: -34,
renderPaused: true,
});
skinViewerImage.camera.rotation.y = -0.5;
async function renderImage(id, image, element) {
await Promise.all([
skinViewerImage.loadSkin(image),
]);
skinViewerImage.background = <%= theme == 'dark' ? 3621201 : 16777215 %>;
skinViewerImage.render();
element.setAttribute('src', skinViewerImage.canvas.toDataURL());
skinViewerImage.dispose();
};
document.querySelectorAll('[skin]').forEach(function (e) {
const skinId = e.getAttribute('skin');
renderImage(skinId, `${TEXTURE_SERVER}/skins/${skinId}.png`, e);
});
</script>
</body>
</html>
7 changes: 5 additions & 2 deletions src/managers/UpdateProfileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import fsp from 'fs/promises';
import Jimp from 'jimp';
import { getFilePath, isDevMode } from '../utils/Utils';
import { generateImageHash } from '../utils/Hash';
import { generateDHash, generateImageHash } from '../utils/Hash';
import { Skin, SkinUsers } from './database/types/SkinTypes';
import { querySync } from './database/MySQLConnection';
import { Cape, CapeUser } from './database/types/CapeTypes';
Expand Down Expand Up @@ -85,7 +85,7 @@ export async function createProfile(uuid: string): Promise<void> {
let skin: Skin = (await querySync('select * from skins where hash = ?', [skinData.hash]))[0];
if (!skin) {
// skin does not exist, so create a new row
await querySync('insert into skins (createdAt, url, skinId, userCount, hash) values (?, ?, ?, ?, ?)', [Date.now().toString(), skinData.url, skinData.hash.substring(20), '1', skinData.hash]);
await querySync('insert into skins (createdAt, url, skinId, userCount, hash, dhash) values (?, ?, ?, ?, ?, ?)', [Date.now().toString(), skinData.url, skinData.hash.substring(20), '1', skinData.hash, skinData.dhash]);
skin = (await querySync('select * from skins where hash = ?', [skinData.hash]))[0];
}

Expand Down Expand Up @@ -316,13 +316,15 @@ async function generateSkinData(url: string): Promise<SkinData | null> {
const width = image.bitmap.width;
const height = image.bitmap.height;
const hash = generateImageHash(image.bitmap.data, width, height);
const dhash = await generateDHash(image.clone());
await fsp.writeFile(`${getFilePath()}/skins/${hash.substring(20)}.png`, await image.getBufferAsync('image/png'));

return {
width,
height,
hash,
url,
dhash,
};
}

Expand Down Expand Up @@ -481,6 +483,7 @@ type SkinData = {
height: number,
hash: string,
url: string,
dhash: string | null,
};

type CapeData = {
Expand Down
1 change: 1 addition & 0 deletions src/managers/database/types/SkinTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type Skin = {
model: boolean,
userCount: number,
hash: string,
dhash: string | null,
};

export type SkinUsers = {
Expand Down
65 changes: 64 additions & 1 deletion src/routes/SkinsRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import fsp from 'fs/promises';
import renderPage from '../utils/RenderPage';
import NodeCache from 'node-cache';
import ErrorManager from '../managers/ErrorManager';
import Jimp from 'jimp';
import { querySync } from '../managers/database/MySQLConnection';
import { Skin, SkinUsers } from '../managers/database/types/SkinTypes';
import { isFileCacheExpired } from '../utils/Utils';
import { generateDHash } from '../utils/Hash';

const app = express.Router();
const MAX_LIMIT = 501;
Expand Down Expand Up @@ -70,11 +72,27 @@ async function generateSkinUserCache(skinId: string, filePath: string): Promise<
fs.writeFileSync('cache/skins.json', JSON.stringify(skinsCache));
})();

(async function () {
const skins: Skin[] = await querySync('select * from skins where dhash is null and url is not null limit 10000');
for (let i = 0; i < skins.length; i++) {
try {
const skin = skins[i];
const image = await Jimp.read(skin.url);
const dhash = await generateDHash(image);

await querySync('update skins set dhash = ? where skinId = ?', [dhash, skin.skinId]);
console.log(`updated ${skin.skinId} with dhash '${dhash}', ${skins.length - i} remain`);
} catch (e) {
console.error(e);
}
}
});//();

app.get('/', (req, res) => res.redirect('/skins/new'));

app.get('/new', async function (req, res) {
try {
const page = parseInt(req.query.page?.toString() || '1');
const page = Math.max(parseInt(req.query.page?.toString() || '1') || 1, 1);
const nPerPage = 35;
if (page > 100) return res.redirect('/skins/new?page=100');

Expand Down Expand Up @@ -138,6 +156,34 @@ app.get('/random', async function (req, res) {
}
});

app.get('/similar', async function (req, res) {
try {
if (!req.query || !req.query.dh) return res.status(400).send('missing required queries');
const page = Math.max(parseInt(req.query.page?.toString() || '1') || 1, 1);
const nPerPage = 32;
const skins: Skin[] = await querySync(`select * from skins where dhash like ? order by createdAt desc limit ${(page - 1) * nPerPage}, ${nPerPage}`, [`${req.query.dh.toString()}%`]);
const count = (await querySync(`select skinId from skins where dhash like ? limit ${page * nPerPage}, ${nPerPage}`, [`${req.query.dh.toString()}%`])).length;
if (skins.length === 0) {
res.write('no skins');
return res.status(404).send();
}

renderPage(req, res, './skins/index', {
skins,
number: page,
skipped: ((page - 1) * nPerPage),
currentPage: '/skins',
random: false,
ads: false,
dh: req.query.dh,
hasNextPage: count > 0,
});
} catch (e) {
console.error(e);
new ErrorManager(req, res, e as Error).write();
}
});

app.get('/:skinId', async function (req, res) {
try {
const skin: Skin = (await querySync('select * from skins where skinId = ?', [req.params.skinId]))[0];
Expand All @@ -156,16 +202,33 @@ app.get('/:skinId', async function (req, res) {
hasMoreUsers = true;
}

let similarSkins: Skin[] = [];
if (skin.dhash) {
similarSkins = await querySync('select skinId, url, enabled from skins where dhash like ? limit 20', [`${skin.dhash.slice(0, 8)}%`]);
if (similarSkins.length > 0) {
similarSkins = similarSkins.filter(s => s.skinId !== skin.skinId);
}
}

renderPage(req, res, './skins/skin', {
skin,
users,
hasMoreUsers,
similarSkins,
});

// check if the users are cached and if they are expired. Re-generate them after the page is loaded
if (cached && isFileCacheExpired(filePath, 60 * 10)) {
await generateSkinUserCache(req.params.skinId, filePath);
}

if (!skin.dhash) {
// if (true) {
const image = await Jimp.read(skin.url);
const dhash = await generateDHash(image);

await querySync('update skins set dhash = ? where skinId = ?', [dhash, skin.skinId]);
}
} catch (e) {
console.error(e);
new ErrorManager(req, res, e as Error).write();
Expand Down
Loading

0 comments on commit fa84d77

Please sign in to comment.