Skip to content

Commit

Permalink
2.4.2
Browse files Browse the repository at this point in the history
- Modify artifact scanning to work better with lower resolutions.
- Fix memory leak with uploading too many screenshots(hopefully)
  • Loading branch information
frzyc committed Dec 29, 2020
1 parent fb7cec2 commit 5015a26
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 77 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"homepage": "https://frzyc.github.io/genshin-optimizer/",
"name": "genshin-optimizer",
"version": "2.4.1",
"version": "2.4.2",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32",
Expand Down
195 changes: 119 additions & 76 deletions src/Artifact/UploadDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useEffect, useState } from 'react';
import { Button, Card, Col, Container, Form, Modal, ProgressBar, Row } from 'react-bootstrap';
import { createWorker } from 'tesseract.js';
import { ArtifactSetsData, ArtifactSlotsData } from '../Data/ArtifactData';
import { ArtifactMainStatsData, ArtifactSetsData, ArtifactSlotsData } from '../Data/ArtifactData';
import scan_art_main from "../imgs/scan_art_main.png";
import Snippet from "../imgs/snippet.png";
import Stat from '../Stat';
import Artifact from './Artifact';
import ReactGA from 'react-ga';

const whiteColor = { r: 250, g: 250, b: 250 } //#FFFFFF
const subStatColor = { r: 80, g: 90, b: 105 } //#495366
const setNameColor = { r: 92, g: 178, b: 86 } //#5CB256
const starColor = { r: 255, g: 204, b: 50 } //#FFCC32

function UploadDisplay(props) {
Expand Down Expand Up @@ -56,12 +53,14 @@ function UploadDisplay(props) {
m.status === "recognizing text" && sProgvariant("success");
sProgress(m.progress);
},
errorHandler: err => console.error(err)
});
await tworker.load();
await tworker.loadLanguage('eng');
await tworker.initialize('eng');
const { data: { text } } = await tworker.recognize(image);
return text
let rec = await tworker.recognize(image);
await tworker.terminate();
return rec
}

const uploadedFile = async (file) => {
Expand All @@ -76,26 +75,26 @@ function UploadDisplay(props) {

let numStars = starScanning(imageDataObj.data, imageDataObj.width, imageDataObj.height)
let awaits = [
// other
ocrImage(imageDataToURL(processImageWithFilter(imageDataObj, whiteColor)), setOtherProgress, setOtherProgVariant),
// substat
ocrImage(imageDataToURL(processImageWithFilter(imageDataObj, subStatColor, 15)), setSubstatProgress, setSubstatProgVariant),
// other is for slotkey and mainStatValue and level
ocrImage(imageDataToURL(processImageWithBandPassFilter(imageDataObj, { r: 140, g: 140, b: 140 }, { r: 255, g: 255, b: 255 })), setOtherProgress, setOtherProgVariant),
// substats
ocrImage(imageDataToURL(processImageWithBandPassFilter(imageDataObj, { r: 65, g: 75, b: 90 }, { r: 160, g: 160, b: 160 }, "bot")), setSubstatProgress, setSubstatProgVariant),
// artifact set
ocrImage(imageDataToURL(processImageWithFilter(imageDataObj, setNameColor)), setArtSetProgress, setArtSetProgVariant),
ocrImage(imageDataToURL(processImageWithBandPassFilter(imageDataObj, { r: 90, g: 160, b: 80 }, { r: 200, g: 255, b: 200 }, "bot")), setArtSetProgress, setArtSetProgVariant),
// main stat
ocrImage(imageDataToURL(processImageWithBandPassFilter(imageDataObj, { r: 150, g: 150, b: 160 }, { r: 215, g: 200, b: 220 })), setMainStatProgress, setMainStatProgVariant)
ocrImage(imageDataToURL(processImageWithBandPassFilter(imageDataObj, { r: 120, g: 120, b: 120 }, { r: 215, g: 200, b: 220 }, "top")), setMainStatProgress, setMainStatProgVariant)
]

let [whiteparsed, substatOCRText, setOCRText, mainStatOCRText] = await Promise.all(awaits)

let setKey = parseSetKey(setOCRText)
let slotKey = parseSlotKey(whiteparsed)
let substats = parseSubstat(substatOCRText)
let level = parseLevel(whiteparsed)
let level = NaN//parseLevel(whiteparsed) looks like the level isnt consistently parsed.
let mainStatKey = parseMainStatKey(mainStatOCRText)
let { mainStatValue, unit = "" } = parseMainStatvalue(whiteparsed)

//so far the main stat value is used to distinguish main stats between % and flat
//the main stat value is used to distinguish main stats between % and flat
if (unit === "%" && (mainStatKey === "hp" || mainStatKey === "def" || mainStatKey === "atk"))
mainStatKey += "_"

Expand All @@ -122,6 +121,34 @@ function UploadDisplay(props) {
if (stats.length > 0) mainStatKey = stats[0]
}
}
let guessLevel = (nStars, mainSKey, mainSVal) => {
//if level isn't parsed, then we try to guess it
let valArr = ArtifactMainStatsData?.[nStars]?.[mainSKey.includes("ele_dmg") ? "ele_dmg" : mainSKey]
if (valArr) {
let isFloat = Stat.getStatUnit(mainSKey) === "%"
let testLevel = valArr.findIndex(val => isFloat ? (Math.abs(mainSVal - val) < 0.1) : (mainSVal === val))
if (testLevel !== -1) {
level = testLevel
return true
}
}
return false
}
//guess level when we have all the stats
if (isNaN(level) && numStars && mainStatKey && mainStatValue)
guessLevel(numStars, mainStatKey, mainStatValue)

//try to guess the level when we only have mainStatKey and mainStatValue
if (isNaN(level) && mainStatKey && mainStatValue) {
let stars = setKey ? Artifact.getRarityArr(setKey) : Object.keys(ArtifactMainStatsData).reverse()//reverse so we check 5* first
for (const nStar of stars)
if (guessLevel(nStar, mainStatKey, mainStatValue)) {
if (!setKey || Artifact.getRarityArr(setKey).includes(nStar)) {
numStars = nStar
break;
}
}
}

let state = {}
if (!isNaN(level)) state.level = level
Expand Down Expand Up @@ -254,10 +281,10 @@ function UploadDisplay(props) {
</Row>)
}
export default UploadDisplay;

let reader = new FileReader()
function fileToURL(file) {
return new Promise(resolve => {
let reader = new FileReader();
// let reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
}
Expand Down Expand Up @@ -331,38 +358,38 @@ function starScanning(pixels, width, height) {
rowsWithNumber = 1;
} else if (lastRowNum) {
rowsWithNumber++
if (rowsWithNumber >= 20) return lastRowNum
if (rowsWithNumber >= 10) return lastRowNum
}
}
return 0;
}
function processImageWithFilter(pixelData, color, threshold = 5) {
// function processImageWithFilter(pixelData, color, region, threshold = 5) {
// let d = Uint8ClampedArray.from(pixelData.data)
// let halfInd = Math.floor(pixelData.width * (pixelData.height / 2) * 4)
// for (let i = 0; i < d.length; i += 4) {
// let outputWhite = true;
// let r = d[i];
// let g = d[i + 1];
// let b = d[i + 2];
// let pixelColor = { r, g, b }
// if (((region === "top" && i < halfInd) || (region === "bot" && i > halfInd) || !region) && colorCloseEnough(pixelColor, color, threshold))
// outputWhite = false
// d[i] = d[i + 1] = d[i + 2] = outputWhite ? 255 : 0
// }
// return new ImageData(d, pixelData.width, pixelData.height)
// }
function processImageWithBandPassFilter(pixelData, color1, color2, region) {
let d = Uint8ClampedArray.from(pixelData.data)
for (let i = 0; i < d.length; i += 4) {
let outputWhite = true;
let r = d[i];
let g = d[i + 1];
let b = d[i + 2];
let pixelColor = { r, g, b }
if (colorCloseEnough(pixelColor, color, threshold))
outputWhite = false
d[i] = d[i + 1] = d[i + 2] = outputWhite ? 255 : 0
}
return new ImageData(d, pixelData.width, pixelData.height)
}

function processImageWithBandPassFilter(pixelData, color1, color2) {
let d = Uint8ClampedArray.from(pixelData.data)
//this also cuts away the bottom half of the picture...
let halfInd = Math.floor(pixelData.width * (pixelData.height / 2) * 4)
for (let i = 0; i < d.length; i += 4) {
let outputWhite = true;
let r = d[i];
let g = d[i + 1];
let b = d[i + 2];
if (i < halfInd && r > color1.r && r < color2.r &&
g > color1.g && g < color2.g &&
b > color1.b && b < color2.b)
if (((region === "top" && i < halfInd) || (region === "bot" && i > halfInd) || !region) &&
r >= color1.r && r <= color2.r &&
g >= color1.g && g <= color2.g &&
b >= color1.b && b <= color2.b)
outputWhite = false
d[i] = d[i + 1] = d[i + 2] = outputWhite ? 255 : 0
}
Expand All @@ -380,19 +407,22 @@ function colorCloseEnough(color1, color2, threshold = 5) {
return false
}

function parseSubstat(text) {
function parseSubstat(recognition, defVal = null) {
let texts = recognition?.data?.lines?.map(line => line.text)
if (!texts) return defVal
let matches = []
//parse substats
Artifact.getSubStatKeys().forEach(key => {
let regex = null
let unit = Stat.getStatUnit(key)
let name = Stat.getStatName(key)
if (unit === "%") regex = new RegExp(name + "\\s*\\+\\s*(\\d+\\.\\d)%", "im");
else regex = new RegExp(name + "\\s*\\+\\s*(\\d+,\\d+|\\d+)($|\\s)", "im");
let match = regex.exec(text)
match && matches.push({ index: match.index, value: match[1], unit, key })
})
matches.sort((a, b) => a.index - b.index)
for (const text of texts) {
//parse substats
Artifact.getSubStatKeys().forEach(key => {
let regex = null
let unit = Stat.getStatUnit(key)
let name = Stat.getStatName(key)
if (unit === "%") regex = new RegExp(name + "\\s*\\+\\s*(\\d+\\.\\d)%", "im");
else regex = new RegExp(name + "\\s*\\+\\s*(\\d+,\\d+|\\d+)($|\\s)", "im");
let match = regex.exec(text)
match && matches.push({ value: match[1], unit, key })
})
}
matches.forEach((match, i) => {
if (i >= 4) return;//this shouldn't happen, just in case
match.value = match.unit === "%" ? parseFloat(match.value) : parseInt(match.value)
Expand All @@ -405,36 +435,49 @@ function parseSubstat(text) {
}
return substats
}
function parseMainStatKey(text) {
for (const key of Artifact.getMainStatKeys())
if (text.toLowerCase().includes(Stat.getStatName(key).toLowerCase()))
return key
function parseMainStatKey(recognition, defVal = "") {
let texts = recognition?.data?.lines?.map(line => line.text)
if (!texts) return defVal
for (const text of texts)
for (const key of Artifact.getMainStatKeys())
if (text.toLowerCase().includes(Stat.getStatName(key).toLowerCase()))
return key
return defVal
}
function parseSetKey(text) {
function parseSetKey(recognition, defVal = "") {
let texts = recognition?.data?.lines?.map(line => line.text)
if (!texts) return defVal
//parse for sets
for (const [key, setObj] of Object.entries(ArtifactSetsData))
if (text.toLowerCase().includes(setObj.name.toLowerCase()))
return key//props.setSetKey(key);
for (const text of texts)
for (const [key, setObj] of Object.entries(ArtifactSetsData))
if (text.toLowerCase().includes(setObj.name.toLowerCase()))
return key//props.setSetKey(key);
}
function parseSlotKey(text) {
function parseSlotKey(recognition, defVal = "") {
let texts = recognition?.data?.lines?.map(line => line.text)
if (!texts) return defVal
//parse for slot
for (const [key, slotObj] of Object.entries(ArtifactSlotsData))
if (text.toLowerCase().includes(slotObj.name.toLowerCase()))
return key;//props.setSlotKey(key);
for (const text of texts)
for (const [key, slotObj] of Object.entries(ArtifactSlotsData))
if (text.toLowerCase().includes(slotObj.name.toLowerCase()))
return key;//props.setSlotKey(key);
}
function parseLevel(text) {
let regex = /\+(\d{1,2})/
let match = regex.exec(text)
if (match) return parseInt(match[1])
return NaN
}
function parseMainStatvalue(text) {
let preText = text.split('+')[0]
let regex = /(\d+\.\d+)%/
let match = regex.exec(preText)
if (match) return { mainStatValue: parseFloat(match[1]), unit: "%" }
regex = /(\d+,\d+|\d{2,3})/
match = regex.exec(preText)
if (match) return { mainStatValue: parseInt(match[1]) }
return { mainStatValue: NaN }
// function parseLevel(text) {
// let regex = /\+(\d{1,2})/
// let match = regex.exec(text)
// if (match) return parseInt(match[1])
// return NaN
// }
function parseMainStatvalue(recognition, defVal = { mainStatValue: NaN }) {
let texts = recognition?.data?.lines?.map(line => line.text)
if (!texts) return defVal
for (const text of texts) {
let regex = /(\d+\.\d)%/
let match = regex.exec(text)
if (match) return { mainStatValue: parseFloat(match[1]), unit: "%" }
regex = /(\d+,\d{3}|\d{2,3})/
match = regex.exec(text)
if (match) return { mainStatValue: parseInt(match[1].replace(/,/g, "")) }
}
return defVal
}
30 changes: 30 additions & 0 deletions src/Data/ArtifactData.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,42 @@ const ArtifactMainSlotKeys = [
]

const ArtifactStarsData = {
// 1: { subsBaselow: 0, subBaseHigh: 0, numUpgradesOrUnlocks: 1 },
// 2: { subsBaselow: 0, subBaseHigh: 1, numUpgradesOrUnlocks: 2 },
3: { subsBaselow: 1, subBaseHigh: 2, numUpgradesOrUnlocks: 3 },
4: { subsBaselow: 2, subBaseHigh: 3, numUpgradesOrUnlocks: 4 },
5: { subsBaselow: 3, subBaseHigh: 4, numUpgradesOrUnlocks: 5 }
};

const ArtifactMainStatsData = {
1: {
hp: [129, 178, 227, 275, 324],
atk: [8, 12, 15, 18, 21],
hp_: [3.1, 4.3, 5.5, 6.7, 7.9],
atk_: [3.1, 4.3, 5.5, 6.7, 7.9],
def_: [3.9, 5.4, 6.9, 8.4, 9.9],
phy_dmg: [3.9, 5.4, 6.9, 8.4, 9.9],
ele_dmg: [3.1, 4.3, 5.5, 6.7, 7.9],
ele_mas: [13, 17, 22, 27, 32],
ener_rech: [3.5, 4.8, 6.1, 7.5, 8.8],
crit_rate: [2.1, 2.9, 3.7, 4.5, 5.3],
crit_dmg: [4.2, 5.8, 7.4, 9.0, 10.5],
heal_bonu: [2.4, 3.3, 4.3, 5.2, 6.1],
},
2: {
hp: [258, 331, 404, 478, 551, 624, 697, 770, 843],
atk: [17, 22, 26, 31, 36, 41, 45, 50, 55],
hp_: [4.2, 5.4, 6.6, 7.8, 9, 10.1, 11.3, 12.5, 13.7],
atk_: [4.2, 5.4, 6.6, 7.8, 9, 10.1, 11.3, 12.5, 13.7],
def_: [5.2, 6.7, 8.2, 9.7, 11.2, 12.7, 14.2, 15.6, 17.1],
phy_dmg: [5.2, 6.7, 8.2, 9.7, 11.2, 12.7, 14.2, 15.6, 17.1],
ele_dmg: [4.2, 5.4, 6.6, 7.8, 9, 10.1, 11.3, 12.5, 13.7],
ele_mas: [17, 22, 26, 31, 36, 41, 45, 50, 55],
ener_rech: [4.7, 6, 7.3, 8.6, 9.9, 11.3, 12.6, 13.9, 15.2],
crit_rate: [2.8, 3.6, 4.4, 5.2, 6, 6.8, 7.6, 8.3, 9.1],
crit_dmg: [5.6, 7.2, 8.8, 10.4, 11.9, 13.5, 15.1, 16.7, 18.3],
heal_bonu: [3.2, 4.1, 5.1, 6, 6.9, 7.8, 8.7, 9.6, 10.5],
},
3: {
hp: [430, 552, 674, 796, 918, 1040, 1162, 1283, 1405, 1527, 1649, 1771, 1893],
atk: [28, 36, 44, 52, 60, 68, 76, 84, 91, 99, 107, 115, 123],
Expand Down

0 comments on commit 5015a26

Please sign in to comment.