Merge pull request #1 from antiboredom/main

little fixes
This commit is contained in:
Kyle McDonald 2025-06-21 12:07:30 -07:00 committed by GitHub
commit f0e79d7495
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 31 additions and 38 deletions

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,5 +1,5 @@
<script> <script>
import { onMount } from "svelte"; import { onMount, tick } from "svelte";
import * as faceapi from "@vladmandic/face-api"; import * as faceapi from "@vladmandic/face-api";
import Cop from "./Cop.svelte"; import Cop from "./Cop.svelte";
@ -13,6 +13,7 @@
let databaseLoaded = $state(false); let databaseLoaded = $state(false);
let uploadedImageSrc = $state(); let uploadedImageSrc = $state();
let matches = $state([]); let matches = $state([]);
let working = $state(false);
let loaded = $derived(faceApiLoaded && databaseLoaded); let loaded = $derived(faceApiLoaded && databaseLoaded);
@ -104,9 +105,13 @@
} }
function handleImageFile(e) { function handleImageFile(e) {
working = true;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (e) { reader.onload = function (e) {
uploadedImageSrc = e.target.result; uploadedImageSrc = e.target.result;
// instead of listening for an onload event in the image
// just call recognize after svelte re-renders
tick().then(recognize);
}; };
reader.readAsDataURL(e.target.files[0]); reader.readAsDataURL(e.target.files[0]);
} }
@ -122,6 +127,7 @@
status = status =
"No faces detected in image. Please try a different image with clearly visible faces."; "No faces detected in image. Please try a different image with clearly visible faces.";
matches = []; matches = [];
working = false;
return; return;
} }
@ -210,6 +216,7 @@
console.error("Error in face recognition:", error); console.error("Error in face recognition:", error);
// updateStatus('Error processing uploaded image for face recognition.', 'red'); // updateStatus('Error processing uploaded image for face recognition.', 'red');
} }
working = false;
} }
function calculateEuclideanDistance(desc1, desc2) { function calculateEuclideanDistance(desc1, desc2) {
@ -227,45 +234,50 @@
<main> <main>
<div class="about-link"> <div class="about-link">
<a href="https://github.com/kylemcdonald/lapd-face-search/" target="_blank" rel="noopener noreferrer" title="About">?</a> <a
href="https://github.com/kylemcdonald/lapd-face-search/"
target="_blank"
rel="noopener noreferrer"
title="About">?</a
>
</div> </div>
<h1>LAPD Face Search</h1> <h1>LAPD Face Search</h1>
<h2>Search over 9,000 LAPD headshots</h2> <h2>Search over 9,000 LAPD headshots</h2>
<p> <p>
Face recognition happens on your device and images are not uploaded. Face recognition happens on your device and images are not uploaded.
<br/>Blurry, low-resolution photos will not match. <br />Blurry, low-resolution photos will not match.
</p> </p>
{#if !loaded} {#if !loaded}
<form> <form>
<label for="image-input" class="loading-label">Loading...</label> <label for="image-input" class="loading-label">Loading...</label>
<input <input id="image-input" type="file" accept="image/*" hidden="" disabled />
id="image-input"
type="file"
accept="image/*"
hidden=""
disabled
/>
</form> </form>
{/if} {/if}
{#if loaded} {#if loaded}
<form> <form>
<label for="image-input">Select Photo</label> <label for="image-input" class={{ "loading-label": working }}>
{#if working}
Processing...
{:else}
Select Photo
{/if}
</label>
<input <input
oninput={handleImageFile} oninput={handleImageFile}
id="image-input" id="image-input"
type="file" type="file"
accept="image/*" accept="image/*"
hidden="" hidden=""
disabled={working}
/> />
</form> </form>
<img <img
bind:this={uploadedImage} bind:this={uploadedImage}
src={uploadedImageSrc} src={uploadedImageSrc}
onload={recognize}
alt="User uploaded face" alt="User uploaded face"
class="uploaded-photo" class="uploaded-photo"
style:display={"none"} style:display={"none"}
@ -294,7 +306,7 @@
<div class="cops"> <div class="cops">
{#each matches as cop} {#each matches as cop}
<div class="cop"> <div class="cop">
<Cop cop={cop.data} index={cop.index} distance={cop.distance} /> <Cop cop={cop.data} index={cop.index} />
</div> </div>
{/each} {/each}
</div> </div>
@ -423,13 +435,6 @@
margin: 2rem 0; margin: 2rem 0;
} }
.results-title {
margin-top: 2em;
margin-bottom: 1em;
border-top: 1px solid #333;
padding-top: 2em;
}
.about-link { .about-link {
position: fixed; position: fixed;
top: 1rem; top: 1rem;
@ -450,7 +455,7 @@
font-weight: bold; font-weight: bold;
font-size: 1.2rem; font-size: 1.2rem;
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
box-shadow: 0 0 8px rgba(0, 0, 0, 1.0); box-shadow: 0 0 8px rgba(0, 0, 0, 1);
} }
.about-link a:hover { .about-link a:hover {
@ -489,7 +494,7 @@
cursor: not-allowed !important; cursor: not-allowed !important;
animation: pulse 1.5s ease-in-out infinite; animation: pulse 1.5s ease-in-out infinite;
} }
.loading-label:hover { .loading-label:hover {
background-color: #666 !important; background-color: #666 !important;
color: #999 !important; color: #999 !important;

View File

@ -1,7 +1,5 @@
<script> <script>
let { cop, distance, index } = $props(); let { cop, index } = $props();
const DEV = import.meta.env.DEV;
const errorImage = const errorImage =
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0yNCAzMkM0MS42NzMxIDMyIDU2IDM3LjM3MjYgNTYgNDRDNTYgNTAuNjI3NCA0MS42NzMxIDU2IDI0IDU2QzYuMzI2ODggNTYgLTggNTAuNjI3NCAtOCA0Qy04IDM3LjM3MjYgNi4zMjY4OCAzMiAyNCAzMloiIGZpbGw9IiM5QTlCOCIvPgo8L3N2Zz4="; "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0yNCAzMkM0MS42NzMxIDMyIDU2IDM3LjM3MjYgNTYgNDRDNTYgNTAuNjI3NCA0MS42NzMxIDU2IDI0IDU2QzYuMzI2ODggNTYgLTggNTAuNjI3NCAtOCA0Qy04IDM3LjM3MjYgNi4zMjY4OCAzMiAyNCAzMloiIGZpbGw9IiM5QTlCOCIvPgo8L3N2Zz4=";

View File

@ -26,6 +26,7 @@ a {
color: #646cff; color: #646cff;
text-decoration: inherit; text-decoration: inherit;
} }
a:hover { a:hover {
color: #535bf2; color: #535bf2;
} }
@ -54,9 +55,11 @@ button {
cursor: pointer; cursor: pointer;
transition: border-color 0.25s; transition: border-color 0.25s;
} }
button:hover { button:hover {
border-color: #646cff; border-color: #646cff;
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 4px auto -webkit-focus-ring-color; outline: 4px auto -webkit-focus-ring-color;
@ -71,16 +74,3 @@ button:focus-visible {
margin: 0 auto; margin: 0 auto;
text-align: center; text-align: center;
} }
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}