예제 #1
0
        private void IdentifyCandidate(DumpFormat dumpFormat, ref MatchCandidate mc, Texture2D mcTex)
        {
            if (dumpFormat.usePeixotoCandidateIdentification)
            {
                IdentifyCandidatePeixoto(dumpFormat, ref mc, mcTex);
                return;
            }

            if (mcTex.GetPixel(0, 0).a < 0.01f || mcTex.GetPixel(mcTex.width - 1, 0).a < 0.01f)
            {
                if (dumpFormat.bgParts.Length == 0)
                {
                    mc.bgPartIndex = 1;
                }
                else
                {
                    mc.bgPartIndex = dumpFormat.bgParts.Length;
                }

                mc.isMask = true;
            }
            else
            {
                //TODO - What if there is no part? (Full bg only)
                //TODO - What if different bg parts have the same size? For now, too bad... :)
                if (dumpFormat.bgParts.Length == 0)
                {
                    //Well we already know it's not a mask so we might be able to assume that it's a BG texture, right? ^^'
                    mc.bgPartIndex = -1;
                }
                else
                {
                    for (var j = 0; j < dumpFormat.bgParts.Length; j++)
                    {
                        if (mcTex.width != dumpFormat.bgParts[j].size.x ||
                            mcTex.height != dumpFormat.bgParts[j].size.y)
                        {
                            continue;
                        }

                        mc.bgPartIndex = j;
                        break;
                    }
                }
            }
        }
예제 #2
0
        private void IdentifyCandidatePeixoto(DumpFormat dumpFormat, ref MatchCandidate mc, Texture2D mcTex)
        {
            //As all the textures are squared and the BG ones are 2 256x256 textures,
            // I can quickly check that as an early identification of mask textures.
            if (mcTex.width != 256)
            {
                mc.bgPartIndex = 2;
                mc.isMask      = true;
                return;
            }

            var bottomLeftColors = mcTex.GetPixels(0, 0, 16, 16);
            var avgColor         = bottomLeftColors.GetAverage();
            var isBgPart         = avgColor.Compare(peixotoBgBandColor) >= 0.999f;

            if (isBgPart)
            {
                var topRightColors = mcTex.GetPixels(mcTex.width - 32, mcTex.height - 32, 32, 32);
                avgColor = topRightColors.GetAverage();
                var isRightBgPart = avgColor.Compare(peixotoRightBgColor) >= 0.999f;

                mc.isMask      = false;
                mc.bgPartIndex = isRightBgPart ? 1 : 0;
            }
            else
            {
                mc.bgPartIndex = 2;
                mc.isMask      = true;
            }

            //sRGB or Linear doesn't seem to make any difference for these old assets.
            // Let's keep the unity default, sRGB.

            // var name = Path.GetFileNameWithoutExtension(fm.fileInfos[mc.fileInfoIndices[0]].Name);
            // var testSrgb = new Texture2D(16, 16, TextureFormat.RGBA32, false, false);
            // testSrgb.SetPixels(bottomLeftColors);
            // fm.SaveTextureToPng(testSrgb, "./", $"{name}_srgb");
            // Object.Destroy(testSrgb);

            // var testLinear = new Texture2D(16, 16, TextureFormat.RGBA32, false, true);
            // testLinear.SetPixels(bottomLeftColors);
            // fm.SaveTextureToPng(testSrgb, "./", $"{name}_linear");
            // Object.Destroy(testLinear);
        }
예제 #3
0
        public IEnumerator MatchTextures(string bgInfoPath, string dumpTexPath, DumpFormat dumpFormat,
                                         TextureMatchingConfig config, System.Action <ProgressInfo> progressCb, System.Action doneCb)
        {
            if (dumpFormat.Equals(baseDumpFormat))
            {
                Debug.LogWarning(string.Format(
                                     "The current base texture format ({0}) is the same as the selected texture format for matching ({1}). This is useless.",
                                     baseDumpFormat.name, dumpFormat.name));
                doneCb();
                yield break;
            }

            reportSb.Clear();
            reportSb.AppendLine("== Texture Matching report ==");
            reportSb.AppendLine("== Texture Matching Started! ==");
            reportSb.AppendLine("Selected Format: " + dumpFormat.name);

            taskTime = Time.unscaledTime;

            progressCb(new ProgressInfo("Loading textures and BgInfos", 0, 0, 0f));
            yield return(new WaitForEndOfFrame());

            yield return(new WaitForEndOfFrame());

            //Load all the bg infos for later
            bgInfoPath = Path.Combine(bgInfoPath, game.ToString());
            var bgInfoCount = fm.LoadFiles(bgInfoPath, "json");
            var bgInfos     = fm.GetObjectsFromFiles <BgInfo>();

            //Load all the file info of the textures to match
            var mcTexCount = fm.LoadFiles(Path.Combine(dumpTexPath, dumpFormat.name), "png",
                                          SearchOption.AllDirectories);

            var candidatesList = new List <MatchCandidate>();

            var texDuplicatesCount = 0;
            var unmatchedTexCount  = 0;
            var unmatchedMcCount   = 0;

            //1. Prepare the MatchCandidates
            for (var i = 0; i < mcTexCount; i++)
            {
                if (i % 50 == 0)
                {
                    progressCb(
                        new ProgressInfo("Preparing candidates", i + 1, mcTexCount, i / (float)(mcTexCount - 1)));
                    yield return(new WaitForEndOfFrame());
                }

                var mcTex = fm.GetTextureFromFileIndex(i);

                var mc = new MatchCandidate();

                mc.md5              = fm.GetMd5(mcTex.GetRawTextureData());
                mc.fileInfoIndices  = new int[] { i };
                mc.texSize.x        = mcTex.width;
                mc.texSize.y        = mcTex.height;
                mc.bgInfoMatchIndex = new List <int>();
                mc.bgInfoMatchValue = new List <float>();

                //Check for duplicates
                var isDuplicate = false;
                for (var j = 0; j < candidatesList.Count; j++)
                {
                    if (candidatesList[j].md5 == mc.md5)
                    {
                        reportSb.AppendLine(string.Concat(mcTex.name, " is a duplicate of ",
                                                          fm.fileInfos[candidatesList[j].fileInfoIndices[0]].Name));
                        texDuplicatesCount++;

                        //int[] indices = new int[candidatesList[j].fileInfoIndices.Length + 1];
                        //for (int k = 0; k < candidatesList[j].fileInfoIndices.Length; k++)
                        //{
                        //    indices[k] = candidatesList[j].fileInfoIndices[k];
                        //}
                        //indices[indices.Length - 1] = j;
                        var indices = candidatesList[j].fileInfoIndices.Concat(new int[] { i }).ToArray();

                        var duplicatedMc = candidatesList[j];
                        duplicatedMc.SetFileInfoIndices(indices);
                        candidatesList[j] = duplicatedMc;

                        isDuplicate = true;

                        //It can't have another duplicate, impossible since I checked for every new element.
                        break;
                    }
                }

                if (isDuplicate)
                {
                    continue;
                }

                //Identify the candidate
                IdentifyCandidate(dumpFormat, ref mc, mcTex);

                //RE3 WARNING - That doesn't WORK AT ALL ON RE3 GAMECUBE
                //Create a special patch for mask texture, thus It will only pick colors into the non fully transparent area.
                var partPatch = new Patch();
                if (mc.isMask)
                {
                    //For some dumping format like Peixoto Before, starting the matching process,
                    // transparency needs to be added by replacing pure black pixels with transparent ones.
                    if (dumpFormat.useBlackAsTransparent)
                    {
                        var colors = mcTex.GetPixels32();
                        for (int j = 0; j < colors.Length; j++)
                        {
                            var c = colors[j];
                            if (c.r + c.g + c.b <= byte.MinValue)
                            {
                                c.a       = byte.MinValue;
                                colors[j] = c;
                            }
                        }

                        mcTex.SetPixels32(colors);
                        mcTex.Apply();

                        // Save the texture with proper transparent pixels.
                        // var fi = fm.fileInfos[i];
                        // Debug.Log(fi.FullName);
                        // var testName = $"{Path.GetFileNameWithoutExtension(fi.Name)}";
                        // fm.SaveTextureToPng(mcTex, fi.DirectoryName, testName);
                    }

                    if (config.inconsistentMaskSize)
                    {
                        partPatch = new Patch(0, mcTex.height - 23, 0, mcTex.height - 23, 128, 23);
                    }
                    else
                    {
                        var line      = mcTex.GetPixels(12, 0, 1, mcTex.height);
                        var foundFlag = false;
                        for (var k = 0; k < line.Length; k++)
                        {
                            if (line[k].a > 0.9f)
                            {
                                partPatch.srcPos.y = k;
                                partPatch.dstPos.y = k;
                                partPatch.size.y   = mcTex.height - k;
                                foundFlag          = true;
                                break;
                            }
                        }

                        if (foundFlag == false || partPatch.size.y < config.histogramPatchSize.y)
                        {
                            unmatchedMcCount++;
                            unmatchedTexCount += mc.fileInfoIndices.Length;
                            reportSb.AppendLine(string.Concat(mcTex.name,
                                                              " seems fully transparent mask. Match it manually."));
                            continue;
                        }

                        line      = mcTex.GetPixels(0, mcTex.height - 4, mcTex.width, 1);
                        foundFlag = false;
                        for (var k = mcTex.width - 1; k >= 0; k--)
                        {
                            if (line[k].a > 0.9f)
                            {
                                partPatch.size.x = k + 1;
                                foundFlag        = true;
                                break;
                            }
                        }

                        if (foundFlag == false || partPatch.size.x < config.histogramPatchSize.x)
                        {
                            unmatchedMcCount++;
                            unmatchedTexCount += mc.fileInfoIndices.Length;
                            reportSb.AppendLine(string.Concat(mcTex.name,
                                                              " seems fully transparent mask. Match it manually."));
                            continue;
                        }
                    }
                }

                //Generate the histogram data
                var histGenAttemptsCount = 0;
                var isMonochromatic      = false;

                mc.histograms  = new Histogram[config.histogramPatchCount];
                mc.HistPatches = new Patch[config.histogramPatchCount];
                //mc.histBgPartPatchIndices = new int[config.histogramPatchCount];

                for (var j = 0; j < config.histogramPatchCount; j++)
                {
                    if (mc.isMask == false)
                    {
                        if (mc.bgPartIndex != -1)
                        {
                            var bgPartPatchIndex = Random.Range(0, dumpFormat.bgParts[mc.bgPartIndex].patches.Length);
                            partPatch = dumpFormat.bgParts[mc.bgPartIndex].patches[bgPartPatchIndex];
                        }
                        else
                        {
                            partPatch = new Patch(0, 0, 0, 0, mc.texSize.x, mc.texSize.y);
                        }
                    }

                    var p = partPatch;

                    p.size = config.histogramPatchSize;

                    var patchPos = partPatch.size - config.histogramPatchSize;
                    patchPos.x = Random.Range(0, patchPos.x);
                    patchPos.y = Random.Range(0, patchPos.y);

                    p.Move(patchPos);

                    var histogram = new Histogram(mc.isMask ? 4 : 3, config.histogramStepCount, 1f);

                    var patchColors = mcTex.GetPixels(p.srcPos.x, p.srcPos.y, p.size.x, p.size.y);

                    histogram.AddValues(patchColors, mc.isMask);

                    //Compare the new histogram with the previous one, if there are the same the texture might be monochromatic...
                    if (j > 0)
                    {
                        if (histogram.Compare(mc.histograms[mc.histograms.Length - 1]) >= 0.999f)
                        {
                            histGenAttemptsCount++;

                            if (histGenAttemptsCount > config.histGenAttemptsMaxCount)
                            {
                                isMonochromatic = true;
                                break;
                            }

                            j--;
                            continue;
                        }
                    }

                    if (config.savePatchTexures)
                    {
                        var test = new Texture2D(p.size.x, p.size.y);
                        test.SetPixels(patchColors);
                        fm.SaveTextureToPng(test, "./test", mcTex.name + "_" + j);
                        Object.Destroy(test);
                    }

                    mc.tempHistogram  = new Histogram(mc.isMask ? 4 : 3, config.histogramStepCount, 1f);
                    mc.histograms[j]  = histogram;
                    mc.HistPatches[j] = p;
                }

                if (isMonochromatic)
                {
                    unmatchedMcCount++;
                    unmatchedTexCount += mc.fileInfoIndices.Length;
                    reportSb.AppendLine(string.Concat(mcTex.name,
                                                      " is too consistent visually to be analyzed properly. Match it manually."));
                }
                else
                {
                    if (mc.bgPartIndex == -1)
                    {
                        mc.bgPartIndex = 0;
                    }

                    //Finally add the candidate to the list
                    candidatesList.Add(mc);
                }


                Object.Destroy(mcTex);
            }

            progressCb(new ProgressInfo("Preparing candidates", mcTexCount, mcTexCount, 1f));
            yield return(new WaitForEndOfFrame());

            //2. The match candidates are now all generated, we can go through all the BgInfo and for each BgInfo, going through all the MCs to find the best match.
            //For sure this way increase risk of false positives but it is also much faster than loading entire textures exponentially...
            var candidates   = candidatesList.ToArray();
            var baseDumpPath = Path.Combine(dumpTexPath, baseDumpFormat.name);
            //int mcMatchesCount = 0;
            var texMatchesCount = 0;

            for (var i = 0; i < bgInfoCount; i++)
            {
                //if (i % 10 == 0)
                //{
                progressCb(new ProgressInfo("Looking for matches", i + 1, bgInfoCount, i / (float)(bgInfoCount - 1)));
                yield return(new WaitForEndOfFrame());

                //}

                if (config.resetDumpMatches)
                {
                    bgInfos[i].ResetDumpMatches(dumpFormat.name);
                }

                //Every candidate is matched, stop searching.
                //TODO - Track the best BgInfo into the MCandidate. This could be perfect. But requires to get rid of that and the isMatched flag stored into the MC.
                //if (mcMatchesCount >= candidates.Length)
                //    break;

                var bgTex   = fm.GetTextureFromPath(Path.Combine(baseDumpPath, bgInfos[i].texDumpMatches[0].texNames[0]));
                var maskTex = bgInfos[i].hasMask
                    ? fm.GetTextureFromPath(Path.Combine(baseDumpPath, bgInfos[i].texDumpMatches[0].texNames[1]))
                    : null;

                //TODO - max possible match for one bg info and one bg part, List suck ass and realistically I never saw 3 textures exactly the same (and there is no duplicate on GC)
                var bestMatchValues =
                    new List <float> [dumpFormat.bgParts.Length == 0 ? 2: dumpFormat.bgParts.Length + 1];
                var bestMatchCandidateIndices =
                    new List <int> [dumpFormat.bgParts.Length == 0 ? 2: dumpFormat.bgParts.Length + 1];

                for (var j = 0; j < bestMatchValues.Length; j++)
                {
                    bestMatchValues[j]           = new List <float>();
                    bestMatchCandidateIndices[j] = new List <int>();
                }

                for (var j = 0; j < candidates.Length; j++)
                {
                    //if (candidates[j].isMatched)
                    //    continue;

                    var mc = candidates[j];

                    //Quick Pruning for mask, compare the size. Masks tend to have different size.
                    if (mc.isMask)
                    {
                        if (maskTex == null)
                        {
                            continue;
                        }

                        //In RE3, the masks have a fixed resolution on GC and a variable on PC (cropped)
                        if (config.inconsistentMaskSize)
                        {
                            if (maskTex.width < 128 || maskTex.height < 23)
                            {
                                continue;
                            }
                        }
                        else
                        {
                            if (maskTex.width != mc.texSize.x || maskTex.height != mc.texSize.y)
                            {
                                continue;
                            }
                        }
                    }
                    else
                    {
                        //This BG texture size comparison was certainly added for re3 but why?
                        //It's actually horrible for RE2 and 1. Thus I added a BG parts length check.
                        if (dumpFormat.bgParts.Length <= 1 && (bgTex.width != mc.texSize.x || bgTex.height != mc.texSize.y))
                        {
                            continue;
                        }
                    }

                    var mcMatchValue = 0f;

                    var isImpossibleMatch = false;
                    for (var k = 0; k < mc.HistPatches.Length; k++)
                    {
                        var p = mc.HistPatches[k];

                        mc.tempHistogram.Reset();

                        Color[] pixels;
                        if (mc.isMask)
                        {
                            if (config.inconsistentMaskSize)
                            {
                                var dstPosY = maskTex.height - 23 + (p.dstPos.y - (256 - 23));
                                pixels = maskTex.GetPixels(p.dstPos.x, dstPosY, p.size.x, p.size.y);
                                mc.tempHistogram.AddValues(pixels, true);
                            }
                            else
                            {
                                pixels = maskTex.GetPixels(p.dstPos.x, p.dstPos.y, p.size.x, p.size.y);
                                mc.tempHistogram.AddValues(pixels, true);
                            }
                        }
                        else
                        {
                            pixels = bgTex.GetPixels(p.dstPos.x, p.dstPos.y, p.size.x, p.size.y);
                            mc.tempHistogram.AddValues(pixels);
                        }

                        if (config.savePatchTexures)
                        {
                            var test = new Texture2D(p.size.x, p.size.y);
                            test.SetPixels(pixels);
                            fm.SaveTextureToPng(test, "./test", (mc.isMask ? maskTex.name : bgTex.name) + "_" + k);
                            Object.Destroy(test);
                        }

                        var patchMatchValue = mc.histograms[k].Compare(mc.tempHistogram);

                        //if the match value of one patch is SO BAD, you can stop here and move to the next MC.
                        if (patchMatchValue <= config.patchMinMatchValue)
                        {
                            isImpossibleMatch = true;
                            break;
                        }

                        mcMatchValue += patchMatchValue;
                    }

                    if (isImpossibleMatch)
                    {
                        continue;
                    }

                    mcMatchValue = mcMatchValue / mc.HistPatches.Length;

                    //bool isFirstValue = bestMatchValues[mc.bgPartIndex].Count <= 0;
                    if (mcMatchValue >= config.candidateMinMatchValue
                        ) // && (isFirstValue || mcMatchValue >= bestMatchValues[mc.bgPartIndex][0]))
                    {
                        //To deal with multiple perfect match... but I haven't already identified duplicates via checksum?
                        //if (!isFirstValue && mcMatchValue != bestMatchValues[mc.bgPartIndex][0])
                        //{
                        //    bestMatchValues[mc.bgPartIndex].Clear();
                        //}

                        bestMatchValues[mc.bgPartIndex].Add(mcMatchValue);
                        bestMatchCandidateIndices[mc.bgPartIndex].Add(j);
                    }
                }

                //Finally we store all the best matches found for that BG info
                //Again going through the bg info (and then the candidates) produces more false positives.
                //But the opposite is crazy bad in term of performance. An exponential amount of disk fetching.

                //For each part (typically 0 = Left, 1 = right, 2 mask)
                var partCount = bestMatchValues.Length;
                //bool hasMatches = false;
                for (var j = 0; j < partCount; j++)
                {
                    var matchCount = bestMatchValues[j].Count;
                    for (var k = 0; k < matchCount; k++)
                    {
                        //it should be defaulted to 0 anyway.
                        if (bestMatchValues[j][k] >= config.candidateMinMatchValue)
                        {
                            //candidates[bestMatchCandidateIndices[j][k]].isMatched = true;
                            var mc = candidates[bestMatchCandidateIndices[j][k]];
                            //for (int l = 0; l < mc.fileInfoIndices.Length; l++)
                            //{
                            //    string texName = fm.RemoveExtensionFromFileInfo(fm.fileInfos[mc.fileInfoIndices[l]]);
                            //    bgInfos[i].AddDumpMatch(dumpFormat.name, texName, mc.bgPartIndex);
                            //    //hasMatches = true;
                            //    texMatchesCount++;
                            //}
                            ////mcMatchesCount++;

                            mc.bgInfoMatchValue.Add(bestMatchValues[j][k]);
                            mc.bgInfoMatchIndex.Add(i);
                        }
                    }
                }

                //if (hasMatches)
                //    fm.SaveToJson(bgInfos[i], bgInfoPath, bgInfos[i].GetFileName(), prettifyJsonOnSave);

                Object.Destroy(bgTex);
                Object.Destroy(maskTex);
            }

            //Analyze the Match candidates' possible bg info matches
            for (var i = 0; i < candidates.Length; i++)
            {
                var mc = candidates[i];
                if (mc.bgInfoMatchIndex.Count <= 0)
                {
                    //Report the unmatchable candidates
                    unmatchedMcCount++;
                    reportSb.Append(string.Concat("Candidate ", i, " can't find a match: "));
                    for (var j = 0; j < candidates[i].fileInfoIndices.Length; j++)
                    {
                        unmatchedTexCount++;
                        reportSb.Append(string.Concat("[", fm.fileInfos[candidates[i].fileInfoIndices[j]].Name, "]"));
                    }

                    reportSb.AppendLine();
                }
                else
                {
                    var bestBgInfoMatchValue = 0f;
                    var bestBgInfoIndex      = 0;
                    for (var j = 0; j < mc.bgInfoMatchIndex.Count; j++)
                    {
                        if (mc.bgInfoMatchValue[j] > bestBgInfoMatchValue)
                        {
                            bestBgInfoMatchValue = mc.bgInfoMatchValue[j];
                            bestBgInfoIndex      = mc.bgInfoMatchIndex[j];
                        }
                    }

                    //Update and save the best bg info match
                    for (var j = 0; j < mc.fileInfoIndices.Length; j++)
                    {
                        var texName = fm.RemoveExtensionFromFileInfo(fm.fileInfos[mc.fileInfoIndices[j]]);

                        bgInfos[bestBgInfoIndex].AddDumpMatch(dumpFormat.name, texName, mc.bgPartIndex);
                        texMatchesCount++;
                    }

                    reportSb.Append(string.Concat("Candidate ", i, " matched: "));
                    for (var j = 0; j < candidates[i].fileInfoIndices.Length; j++)
                    {
                        reportSb.Append(string.Concat("[", fm.fileInfos[candidates[i].fileInfoIndices[j]].Name, "]"));
                    }

                    reportSb.Append(string.Concat(" - ", bgInfos[bestBgInfoIndex].namePrefix));
                    reportSb.Append(string.Concat(" - ", bestBgInfoMatchValue.ToString("0.00")));
                    reportSb.AppendLine();

                    fm.SaveToJson(bgInfos[bestBgInfoIndex], bgInfoPath, bgInfos[bestBgInfoIndex].GetFileName(),
                                  prettifyJsonOnSave);
                }
            }

            reportSb.AppendLine(string.Format(
                                    "{0} matches for {1} textures ({2} duplicates). {3} unmatchable textures from {4} candidates.",
                                    texMatchesCount, mcTexCount, texDuplicatesCount, unmatchedTexCount, unmatchedMcCount));
            reportSb.AppendLine(string.Format("== Texture Matching done! ({0} seconds) ==",
                                              (Time.unscaledTime - taskTime).ToString("#.0")));

            fm.OpenFolder(bgInfoPath);

            doneCb();
        }