jclyo1's picture
updates
5ba48ce
raw
history blame
31.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1">
<!-- Bootstrap CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"
></script>
<link rel="stylesheet" href="style.css" type="text/css" />
<script
type="module"
src="https://display.truepic.com/truepic_display.es.js"
></script>
<link rel="stylesheet" href="json_viewer.css" />
<script src="https://unpkg.com/@peculiar/x509"></script>
<script type="text/javascript" src="json_viewer.js"></script>
</head>
<body>
<div class="container-fluid mt-2" id="head">
<div class="container-xl">
<div class="row">
<div class="col position-relative">
<div class="row">
<div class="col">
<h1>
Watermarked Content Credentials
<div class="badge bg-secondary">experimental</div>
</h1>
<p>
Generate images with C2PA content credentials and Steg.ai
digital watermarking for enhanced AI label robustness
</p>
<div class="alert alert-primary d-md-none" role="alert">
<h4>Support our work!</h4>
<p>
<img src="images/heart_icon.svg" /> Like this Space in the nav
above
</p>
<p>
<img src="images/wave_icon.svg" /> Joining the conversation in
the Community tab
</p>
</div>
</div>
</div>
<div class="d-flex flex-row position-absolute bottom-0 tabs">
<div id="generateTab" class="active">
<img src="images/generate_icon.svg" /> Generate
</div>
<div id="verifyTab">
<img src="images/verify_icon.svg" /> Verify
</div>
</div>
</div>
<div class="col right-column d-none d-md-block">
<div class="alert alert-primary" role="alert">
<h4>Support our work!</h4>
<p>
<img src="images/heart_icon.svg" /> Like this Space in the nav
above
</p>
<p>
<img src="images/wave_icon.svg" /> Joining the conversation in
the Community tab
</p>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid" id="body">
<div class="container-xl">
<div class="row">
<div class="col" style="min-width: 0; padding-right: 0">
<div class="display-generate">
<div class="image-container">
<img src="images/placeholder.png" class="placeholder" />
<div class="spinner" style="display: none">
<img src="images/spinner.svg" />
<h3 class="mt-3">Generating image...</h3>
<p class="small">In some cases, this may take 1-2 minutes.</p>
</div>
</div>
<div class="alert alert-secondary action-menu" role="alert">
<div class="row">
<div class="col">
<p class="mb-0">
<strong
>Download the signed, watermarked image to share
it.</strong
>
</p>
<p class="mb-0">
Use the verify tab two view the details for an image you
have or that's been shared with you.
</p>
</div>
<div class="col flex-grow-0">
<a id="download-button" class="btn btn-outline-primary"
>Download image <img src="images/download_icon.svg"
/></a>
</div>
<div class="col flex-grow-0">
<button
type="button"
id="goto-verify-button"
class="btn btn-outline-primary"
>
Go to verify tab <img src="images/link_icon.svg" />
</button>
</div>
</div>
</div>
</div>
<div class="display-verify">
<div id="original-image">
<div class="row mt-5">
<div class="col mb-4">
<h2>Uploaded image</h2>
</div>
</div>
<div class="row pb-4">
<div class="col flex-grow-0">
<img id="uploaded-image" class="thumbnail" />
</div>
<div class="col">
<div id="content-crendentials-icon"></div>
Content Credentials<br />
<div id="digital-watermark-icon"></div>
Digital watermark
</div>
</div>
</div>
<div class="mt-3" id="resultLabel">
<h2 class="mb-4">Result</h2>
<div
class="alert alert-secondary"
role="alert"
id="verifyResultDescription"
></div>
</div>
<div class="image-container">
<img src="images/placeholder.png" class="placeholder" />
<div class="spinner" style="display: none">
<img src="images/spinner.svg" />
<h3 class="mt-3">Verifying image...</h3>
<p class="small">In some cases, this may take 1-2 minutes.</p>
</div>
</div>
<div class="output-container mt-5" style="display: none">
<h3 class="mt-4 mb-4">Verification output</h3>
<div id="verification-output"></div>
<h3 class="mt-4 pt-4 mb-4" id="certificates-h3">
Certificates
</h3>
<div class="certificate" id="certificate-output">
<h4>Certificate chain</h4>
<ul id="certificate-list"></ul>
<h4>Details</h4>
<div class="details">
<strong>Basic info</strong>
<div>
<label>Type</label>
X.509 Certificate
</div>
<div>
<label>Serial Number</label>
<div class="serialNumber"></div>
</div>
<div>
<label>Issued</label>
<div class="issued"></div>
</div>
<div>
<label>Expired</label>
<div class="expired"></div>
</div>
<strong>Subject</strong>
<div>
<label>Common Name</label>
<div class="subjectCommonName"></div>
</div>
<div>
<label>Organization</label>
<div class="subjectOrganization"></div>
</div>
<div>
<label>Organization Unit</label>
<div class="subjectOrganizationUnit"></div>
</div>
<div>
<label>Country</label>
<div class="subjectCountry"></div>
</div>
<strong>Issuer</strong>
<div>
<label>Common Name</label>
<div class="issuerCommonName"></div>
</div>
<div>
<label>Organization</label>
<div class="issuerOrganization"></div>
</div>
<div>
<label>Organization Unit</label>
<div class="issuerOrganizationUnit"></div>
</div>
<div>
<label>Country</label>
<div class="issuerCountry"></div>
</div>
<strong>Public Key Info</strong>
<div>
<label>Algorithm</label>
<div class="algorithm"></div>
</div>
<div id="modulusContainer">
<label>Modulus</label>
<div class="modulus"></div>
</div>
<div id="curveContainer">
<label>Curve</label>
<div class="namedCurve"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col mt-4 pt-4" id="logo">
<img src="images/logo.png" />
<p class="small mt-3 mb-5">
This is a conceptual application meant for demonstration
purposes only.
</p>
</div>
</div>
<div class="col right-column">
<div class="display-generate">
<form class="image-gen-form">
<div class="form-group mb-3">
<label for="prompt">Image prompt</label>
<textarea id="prompt" class="form-control"></textarea>
</div>
<div class="form-group mb-3">
<label>Model</label>
<div class="custom-select">
<select id="model" class="form-control">
<option disabled selected value>Select</option>
<option value="runwayml/stable-diffusion-v1-5,1.5">
runwayml/stable-diffusion-v1-5
</option>
<option value="CompVis/stable-diffusion-v1-4,1.4">
CompVis/stable-diffusion-v1-4
</option>
<option value="stabilityai/stable-diffusion-2-1,2.1">
stabilityai/stable-diffusion-2-1
</option>
</select>
</div>
</div>
<div class="form-check mb-3">
<input
class="form-check-input"
type="checkbox"
value=""
id="generate-terms"
/>
<label class="form-check-label" for="defaultCheck1">
By using this demo you agree to the terms and conditions of
<a
href="https://truepic.com/terms-and-conditions/"
target="_blank"
>Truepic</a
>
and
<a href="https://steg.ai/tos.html" target="_blank">Steg.ai</a>
</label>
</div>
<button
type="submit"
class="btn btn-primary"
id="generate-button"
>
Submit
</button>
<div class="error mt-3">
Something went wrong. Please try again.
</div>
</form>
<div class="how-it-works">
<p><strong>How it works</strong></p>
<p>
When an image is generated and signed with C2PA Content
Credentials, an imperceptible digital watermark, powered by
Steg.ai, is also added to the image pixels. The watermark
serves as a backup in case the Content Credentials are lost,
such as when sharing the image between currently incompatible
services like text messaging. By using the watermark, it is
possible to retrieve a restored, signed version of the image
from before the data was decoupled, which you can try in the
verify tab.
</p>
</div>
</div>
<div class="display-verify">
<form class="verify-upload-form" enctype="multipart/form-data">
<div class="form-group mb-3">
<label>Upload image</label>
<input
type="file"
class="form-control"
name="fileUpload"
id="fileUpload"
/>
</div>
<div class="form-check mb-3">
<input
class="form-check-input"
type="checkbox"
value=""
id="verify-terms"
/>
<label class="form-check-label" for="defaultCheck1">
By using this demo you agree to the terms and conditions of
<a
href="https://truepic.com/terms-and-conditions/"
target="_blank"
>Truepic</a
>
and
<a href="https://steg.ai/tos.html" target="_blank">Steg.ai</a>
</label>
</div>
<button
type="submit"
class="btn btn-primary"
id="verify-button"
>
Submit
</button>
<div class="error mt-3">
Something went wrong. Please try again.
</div>
</form>
<div class="how-it-works">
<p><strong>How it works</strong></p>
<p>
When an image is generated and signed with C2PA Content
Credentials, an imperceptible digital watermark, powered by
Steg.ai, is also added to the image pixels. The watermark
serves as a backup in case the Content Credentials are lost,
such as when sharing the image between currently incompatible
services like text messaging. By using the watermark, it is
possible to retrieve a restored, signed version of the image
from before the data was decoupled, which you can try in the
verify tab.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
const generateTab = document.querySelector("#generateTab");
const verifyTab = document.querySelector("#verifyTab");
const displayVerify = document.querySelectorAll(".display-verify");
const displayGenerate = document.querySelectorAll(".display-generate");
const uploadForm = document.querySelector(".verify-upload-form");
const imageGenForm = document.querySelector(".image-gen-form");
const imagePrompt = document.getElementById("prompt");
const model = document.getElementById("model");
const generateTerms = document.getElementById("generate-terms");
const generateButton = document.getElementById("generate-button");
const fileUpload = document.getElementById("fileUpload");
const verifyButton = document.getElementById("verify-button");
const verifyTerms = document.getElementById("verify-terms");
const generateImageContainer = document.querySelector(
".display-generate .image-container"
);
const generateActionMenu = document.querySelector(".action-menu");
const verifyImageContainer = document.querySelector(
".display-verify .image-container"
);
const uploadedImageContainer = document.querySelector("#original-image");
const downloadButton = document.getElementById("download-button");
const verifyResultDescription = document.getElementById(
"verifyResultDescription"
);
const verificationOutput = document.getElementById("verification-output");
const certificateList = document.getElementById("certificate-list");
const outputContainer = document.querySelector(".output-container");
const contentCredentialsIcon = document.getElementById(
"content-crendentials-icon"
);
const digitalWatermarkIcon = document.getElementById(
"digital-watermark-icon"
);
var certificates = [];
[imagePrompt, model, generateTerms].forEach((item) => {
item.addEventListener("change", async (event) => {
setGenerateButtonStatus();
});
});
const setGenerateButtonStatus = () => {
if (imagePrompt.value && model.value && generateTerms.checked)
generateButton.classList.add("active");
else generateButton.classList.remove("active");
};
[fileUpload, verifyTerms].forEach((item) => {
item.addEventListener("change", async (event) => {
setVerifyButtonStatus();
});
});
const setVerifyButtonStatus = () => {
if (fileUpload.value && verifyTerms.checked)
verifyButton.classList.add("active");
else verifyButton.classList.remove("active");
};
generateTab.addEventListener("click", (event) => {
event.target.classList.add("active");
verifyTab.classList.remove("active");
setGenerateElementsDisplay("block");
setVerifyElementsDisplay("none");
});
verifyTab.addEventListener("click", (event) => {
event.target.classList.add("active");
generateTab.classList.remove("active");
setGenerateElementsDisplay("none");
setVerifyElementsDisplay("block");
});
document
.getElementById("goto-verify-button")
.addEventListener("click", (event) => {
verifyTab.classList.add("active");
generateTab.classList.remove("active");
setVerifyElementsDisplay("block");
setGenerateElementsDisplay("none");
});
function setGenerateElementsDisplay(displayStatus) {
displayGenerate.forEach((item) => {
item.style.display = displayStatus;
});
}
function setVerifyElementsDisplay(displayStatus) {
displayVerify.forEach((item) => {
item.style.display = displayStatus;
});
}
uploadForm.addEventListener("submit", (e) => {
e.preventDefault();
submitForm();
});
function submitForm() {
if (!fileUpload.value || !verifyTerms.checked) return;
const file = document.getElementById("fileUpload").files[0];
let fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function () {
document
.getElementById("uploaded-image")
.setAttribute("src", fileReader.result);
};
document.querySelector(".display-verify .error").style.display = "none";
placeholder = document.querySelector(".display-verify .placeholder");
spinner = document.querySelector(".display-verify .spinner");
placeholder.style.display = "none";
if (document.getElementById("verifyresult"))
document.getElementById("verifyresult").remove(); // JCL make sure to remove the correct one
spinner.style.display = "block";
outputContainer.style.display = "none";
document.getElementById("resultLabel").style.display = "none";
uploadedImageContainer.style.display = "none";
const formData = new FormData(uploadForm);
// Add additional form data as needed
//formData.append('additionalData', 'additionalValue');
// Call function to submit form data
submitFormData(formData);
}
function submitFormData(formData) {
fetch("verify", {
method: "POST",
body: formData,
})
.then((response) => response.json())
.then((data) => {
console.log(data);
fileUpload.value = "";
setVerifyButtonStatus();
if (data.contains_c2pa == "true") {
contentCredentialsIcon.setAttribute("class", "check-icon");
} else {
contentCredentialsIcon.setAttribute("class", "x-icon");
}
if (data.contains_watermark == "true") {
digitalWatermarkIcon.setAttribute("class", "check-icon");
} else {
digitalWatermarkIcon.setAttribute("class", "x-icon");
}
if (
data.contains_c2pa == "true" &&
data.contains_watermark == "true"
) {
verifyResultDescription.innerHTML =
"Your image contains content credentials, which are displayed below.";
} else if (
data.contains_c2pa == "false" &&
data.contains_watermark == "true"
) {
verifyResultDescription.innerHTML =
"The watermark was found, but image modifications were also detected. The last untampered, signed version on file is displayed.";
} else if (
data.contains_c2pa == "true" &&
data.contains_watermark == "false"
) {
verifyResultDescription.innerHTML =
"Your image contains content credentials, which are displayed below. A watermark was not detected.";
} else if (
data.contains_c2pa == "false" &&
data.contains_watermark == "false"
) {
verifyResultDescription.innerHTML =
"Nothing to show: this image contains neither content credentials, nor a watermark.";
}
uploadedImageContainer.style.display = "flex";
document.getElementById("resultLabel").style.display = "block";
document.querySelector(".display-verify .spinner").style.display =
"none";
if (data.result_media != "n/a") {
const path = "/" + data.result_media;
var truepicDisplay = document.createElement("truepic-display");
truepicDisplay.addEventListener(
"validate",
setVerificationOutputFromValidation
);
truepicDisplay.addEventListener(
"validate",
setCertificateOutputFromValidation
);
truepicDisplay.setAttribute("id", "verifyresult");
truepicDisplay.setAttribute("active", "");
var truepic = document.createElement("img");
truepic.src = path;
truepicDisplay.appendChild(truepic);
verifyImageContainer.appendChild(truepicDisplay);
outputContainer.style.display = "block";
}
// Handle response data
})
.catch((error) => {
console.log(error);
document.querySelector(".display-verify .error").style.display =
"block";
fileUpload.value = "";
setVerifyButtonStatus();
document.querySelector(".display-verify .spinner").style.display =
"none";
document.querySelector(".display-verify .placeholder").style.display =
"block";
// Handle errors
});
}
const generateImage = async (text, model) => {
const inferResponse = await fetch(
`generate?prompt=${text}&model=${model}`
);
const inferJson = await inferResponse.json();
return inferJson.response;
};
imageGenForm.addEventListener("submit", async (event) => {
event.preventDefault();
if (!imagePrompt.value || !model.value || !generateTerms.checked) return;
placeholder = document.querySelector(".display-generate .placeholder");
spinner = document.querySelector(".display-generate .spinner");
generateActionMenu.style.display = "none";
document.querySelector(".display-generate .error").style.display = "none";
// parameters.style.display = "none";
try {
placeholder.style.display = "none";
if (document.getElementById("genresult"))
document.getElementById("genresult").remove(); // JCL make sure to remove the correct one
spinner.style.display = "block";
const resp = await generateImage(imagePrompt.value, model.value);
const path = "/" + resp;
var truepicDisplay = document.createElement("truepic-display");
truepicDisplay.setAttribute("id", "genresult");
truepicDisplay.setAttribute("active", "");
var truepic = document.createElement("img");
truepic.src = path;
truepicDisplay.appendChild(truepic);
spinner.style.display = "none";
generateImageContainer.appendChild(truepicDisplay);
downloadButton.href = path;
downloadButton.download = resp;
generateActionMenu.style.display = "block";
/*
modelParam.innerHTML = model.value;
promptParam.innerHTML = textGenInput.value;
parameters.style.display = "block";
*/
} catch (err) {
console.error(err);
document.querySelector(".display-generate .error").style.display =
"block";
spinner.style.display = "none";
placeholder.style.display = "block";
}
});
function setVerificationOutputFromValidation(event) {
//verificationDetails.style.display = "block";
return setVerificationOutput(event.detail.manifestStore.toJSON());
}
function setCertificateOutputFromValidation(event) {
return setCertificateOutput(event.detail.manifestStore);
}
function setVerificationOutput(output = null) {
verificationOutput.innerHTML = "";
if (!output) {
return;
}
const viewer = new JSONViewer();
verificationOutput.appendChild(viewer.getContainer());
viewer.showJSON(output);
}
function setCertificateOutput(manifestStore = null) {
const certificate = manifestStore?.activeManifest?.certificate;
if (!certificate) {
return;
}
certificates = [
{
der: certificate.der,
name: certificate.subjectName,
decoded: new x509.X509Certificate(certificate.der),
},
...certificate.chain.map((certificate) => ({
der: certificate.der,
decoded: new x509.X509Certificate(certificate.der),
})),
];
certificates.forEach((certificate) => {
certificate.transformed = transformCert(certificate.decoded);
});
console.log("certificates", certificates);
certificateList.innerHTML = "";
certificates.forEach((certificate, index) => {
var li = document.createElement("li");
if (index == 0) li.classList.add("active");
li.appendChild(
document.createTextNode(certificate.transformed.subjectCommonName)
);
li.addEventListener("click", function (e) {
setCertificate(index);
const lis = document.querySelectorAll("#certificate-list li");
lis.forEach((element) => {
element.classList.remove("active");
});
this.classList.add("active");
});
certificateList.appendChild(li);
});
setCertificate(0);
}
function transformCert(certificate) {
const {
issuer,
subject,
notAfter: expired,
notBefore: issued,
serialNumber,
publicKey: {
algorithm: {
name: algorithm,
modulusLength: modulus,
namedCurve: namedCurve,
},
},
} = certificate;
const parsedSubject = parseCertificateValues(subject);
const parsedIssuer = parseCertificateValues(issuer);
return {
issuerCommonName: parsedIssuer["CN"],
issuerOrganizationUnit: parsedIssuer["OU"],
issuerOrganization: parsedIssuer["O"],
issuerCountry: parsedIssuer["C"],
subjectCommonName: parsedSubject["CN"],
subjectOrganizationUnit: parsedSubject["OU"],
subjectOrganization: parsedSubject["O"],
subjectCountry: parsedSubject["C"],
issued,
expired,
serialNumber,
algorithm,
modulus,
namedCurve,
};
}
function parseCertificateValues(input) {
const params = new URLSearchParams(input.replaceAll(",", "&"));
const responses = {};
for (const entry of params.entries()) {
responses[entry[0].trim()] = entry[1];
}
return responses;
}
function setCertificate(ind) {
const certificate = certificates[ind].transformed;
document.querySelector(".details .issuerCommonName").innerHTML =
certificate.issuerCommonName;
document.querySelector(".details .issuerOrganizationUnit").innerHTML =
certificate.issuerOrganizationUnit;
document.querySelector(".details .issuerOrganization").innerHTML =
certificate.issuerOrganization;
document.querySelector(".details .issuerCountry").innerHTML =
certificate.issuerCountry;
document.querySelector(".details .subjectCommonName").innerHTML =
certificate.subjectCommonName;
document.querySelector(".details .subjectOrganizationUnit").innerHTML =
certificate.subjectOrganizationUnit;
document.querySelector(".details .subjectOrganization").innerHTML =
certificate.subjectOrganization;
document.querySelector(".details .subjectCountry").innerHTML =
certificate.subjectCountry;
document.querySelector(".details .issued").innerHTML = certificate.issued;
document.querySelector(".details .expired").innerHTML =
certificate.expired;
document.querySelector(".details .serialNumber").innerHTML =
certificate.serialNumber;
document.querySelector(".details .algorithm").innerHTML =
certificate.algorithm;
/*
if (certificate.modulus !== undefined) {
document.querySelector(".details .modulus").innerHTML =
certificate.modulus;
document.querySelector("#modulusContainers").style.display = "block";
} else {
document.querySelector("#modulusContainers").style.display = "none";
}
*/
if (certificate.namedCurve !== undefined) {
document.querySelector(".details .namedCurve").innerHTML =
certificate.namedCurve;
document.querySelector("#curveContainer").style.display = "block";
} else {
document.querySelector("#curveContainer").style.display = "none";
}
}
</script>
</html>