Index the number of transparent pixels

This commit is contained in:
Jordan Eldredge 2022-10-05 19:51:00 -07:00
parent 284ba82541
commit 1c49e29030
5 changed files with 130 additions and 13 deletions

View file

@ -19,7 +19,7 @@ import fetch from "node-fetch";
import JSZip from "jszip";
import fs from "fs/promises";
import path from "path";
import regionParser from "../regionParser";
import { getTransparentAreaSize } from "../transparency";
export const IS_README = /(file_id\.diz)|(\.txt)$/i;
// Skinning Updates.txt ?
@ -340,18 +340,17 @@ export default class SkinModel {
// Has transparency
async hasTransparency(): Promise<boolean> {
const size = await this.transparentAreaSize();
return size > 0;
}
async transparentPixels(): Promise<number> {
const region = await this._getFile("region", "txt");
if (region == null) {
return false;
return 0;
}
const text = await region.getTextContent();
if (text == null) {
return false;
}
const regions = regionParser(text);
return Object.values(regions).some((region) => region.length > 0);
return getTransparentAreaSize(text);
}
async hasAni(): Promise<boolean> {

View file

@ -9,6 +9,7 @@ import * as CloudFlare from "../CloudFlare";
import SkinModel from "./SkinModel";
import UserContext from "./UserContext";
import TweetModel from "./TweetModel";
import { TweetStatus } from "../types";
export const SKIN_TYPE = {
CLASSIC: 1,
@ -158,17 +159,20 @@ export async function getUploadStatuses(
type SearchIndex = {
objectID: string;
md5: string;
// TODO: Can be deprecated in favor of tweetStatus
nsfw: boolean;
tweetStatus: TweetStatus;
fileName: string;
twitterLikes: number;
equalizer: boolean;
playlist: boolean;
browser: boolean;
media: boolean;
general: boolean;
video: boolean;
cur: boolean;
ani: boolean;
transparency: boolean;
transparentPixels: number;
mikro: boolean;
vidamp: boolean;
avs: boolean;
@ -186,6 +190,9 @@ async function getSearchIndexes(
return Promise.all(
skins.map(async (skin) => {
if (skin.getSkinType() !== "CLASSIC") {
throw new Error("Only classic skins are supported");
}
const readmeText = await skin.getReadme();
const tweets = await skin.getTweets();
const likes = tweets.reduce((acc: number, tweet: TweetModel) => {
@ -195,17 +202,19 @@ async function getSearchIndexes(
objectID: skin.getMd5(),
md5: skin.getMd5(),
nsfw: await skin.getIsNsfw(),
tweetStatus: await skin.getTweetStatus(),
readmeText: readmeText ? truncate(readmeText, 4800) : null,
fileName: await skin.getFileName(),
twitterLikes: likes,
equalizer: await skin.hasEqualizer(),
media: await skin.hasMediaLibrary(),
playlist: await skin.hasPlaylist(),
browser: await skin.hasBrowser(),
general: await skin.hasGeneral(),
video: await skin.hasVideo(),
cur: await skin.hasCur(),
ani: await skin.hasAni(),
transparency: await skin.hasTransparency(),
transparentPixels: await skin.transparentPixels(),
mikro: await skin.hasMikro(),
vidamp: await skin.hasVidamp(),
avs: await skin.hasAVS(),
@ -219,8 +228,6 @@ export async function updateSearchIndexs(
md5s: string[]
): Promise<any> {
const skinIndexes = await getSearchIndexes(ctx, md5s);
console.log(skinIndexes);
return;
const results = await searchIndex.partialUpdateObjects(skinIndexes, {
createIfNotExists: true,

View file

@ -34,6 +34,7 @@
"lru-cache": "^6.0.0",
"md5": "^2.2.1",
"node-fetch": "^2.6.7",
"polygon-clipping": "^0.15.3",
"puppeteer": "^13.3.2",
"sharp": "^0.28.3",
"sqlite3": "^5.0.2",

View file

@ -0,0 +1,98 @@
import polygonClipping, {
Polygon,
MultiPolygon,
Pair,
Ring,
} from "polygon-clipping";
import regionParser from "./regionParser";
const HEIGHT = 116;
const SHADE_HEIGHT = 14;
const WIDTH = 275;
const largeWindowBounds = [[makeBox(WIDTH, HEIGHT)]] as MultiPolygon;
const shadeWindowBounds = [[makeBox(WIDTH, SHADE_HEIGHT)]] as MultiPolygon;
const maxBounds: { [name: string]: MultiPolygon } = {
normal: largeWindowBounds,
equalizer: largeWindowBounds,
windowshade: shadeWindowBounds,
equalizerws: shadeWindowBounds,
};
export function getTransparentAreaSize(regionTxt: string): number {
const regions = regionParser(regionTxt);
let totalTransparentArea = 0;
for (const _name of Object.keys(regions)) {
const name = _name.toLocaleLowerCase();
const region = regions[name];
if (region == null || region.length === 0) {
continue;
}
const max = maxBounds[name];
if (max == null) {
// This is an invalid region.
continue;
}
const opaque = regionStringsToPolygons(region);
const transparent = polygonClipping.difference(max, opaque);
const transparentArea = getMultipolygonArea(transparent);
totalTransparentArea += transparentArea;
}
return totalTransparentArea;
}
// Compute the area of a multi-polygon.
// Note: If there are overlaps they might be computed multiple times.
function getMultipolygonArea(multipolygon: MultiPolygon) {
let area = 0;
for (const polygon of multipolygon) {
for (const ring of polygon) {
area += getArea(ring);
}
}
return area;
}
// Make a box polygon with the given width and height.
function makeBox(width: number, height: number): Ring {
return [
[0, 0],
[width, 0],
[width, height],
[0, height],
[0, 0],
];
}
// Given a parsed list of region.txt polygon strings, return a list of polygons
// which are closed (end where they begin).
function regionStringsToPolygons(region: string[]): Polygon[] {
return region.map((p) => {
const points: Ring = p.split(" ").map((p) => {
return p.split(",").map((n) => parseInt(n, 10)) as Pair;
});
// Ensure these close properly.
points.push(points[0]);
return [points];
});
}
// Get the area of a single Ring (simple polygon).
// Assumes the polygon is closed (ends where it begins).
function getArea(points: Ring) {
let det = 0;
const l = points.length;
if (l < 3) {
return 0;
}
for (let i = 1; i < l; i++) {
const p1 = points[i - 1];
const p2 = points[i];
det += p1[0] * p2[1] - p1[1] * p2[0];
}
return Math.abs(det) / 2;
}

View file

@ -20023,6 +20023,13 @@ pngparse@^2.0.1:
resolved "https://registry.yarnpkg.com/pngparse/-/pngparse-2.0.1.tgz#86852de4de349f4efb1e852e7525655e5ac5dfb8"
integrity sha512-RyB1P0BBwt3CNIZ5wT53lR1dT3CUtopnMOuP8xZdHjPhI/uXNNRnkx1yQb/3MMMyyMeo6p19fiIRHcLopWIkxA==
polygon-clipping@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/polygon-clipping/-/polygon-clipping-0.15.3.tgz#0215840438470ba2e9e6593625e4ea5c1087b4b7"
integrity sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==
dependencies:
splaytree "^3.1.0"
portfinder@^1.0.13, portfinder@^1.0.26:
version "1.0.26"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70"
@ -23631,6 +23638,11 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
splaytree@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-3.1.1.tgz#e1bc8e68e64ef5a9d5f09d36e6d9f3621795a438"
integrity sha512-9FaQ18FF0+sZc/ieEeXHt+Jw2eSpUgUtTLDYB/HXKWvhYVyOc7h1hzkn5MMO3GPib9MmXG1go8+OsBBzs/NMww==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"