diff --git a/package.json b/package.json index 4b0b864ff1..127286c618 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Artifact/UploadDisplay.js b/src/Artifact/UploadDisplay.js index 08adbe5d2b..8e19d3c91f 100644 --- a/src/Artifact/UploadDisplay.js +++ b/src/Artifact/UploadDisplay.js @@ -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) { @@ -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) => { @@ -76,14 +75,14 @@ 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) @@ -91,11 +90,11 @@ function UploadDisplay(props) { 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 += "_" @@ -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 @@ -254,10 +281,10 @@ function UploadDisplay(props) { ) } 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); } @@ -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 } @@ -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) @@ -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 } \ No newline at end of file diff --git a/src/Data/ArtifactData.js b/src/Data/ArtifactData.js index 446542065e..298e8b6224 100644 --- a/src/Data/ArtifactData.js +++ b/src/Data/ArtifactData.js @@ -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],