Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions index.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,31 @@ nav {
}
}
}

#loadingOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}

#loadingBarContainer {
width: 80%;
max-width: 600px;
background: var(--preview-border-color);
border: 2px solid var(--border-color);
}

#loadingBar {
height: 20px;
background: var(--nav-accent);
width: 0%;
transition: width 0.3s ease;
}
12 changes: 10 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,16 @@ <h2>Output Settings</h2>
<button id="regenButton">Reset</button>
<button id="savePatternButton">Save</button>
</div>
<div id="loadingOverlay">
<div id="loadingBarContainer">
<div id="loadingBar"></div>
</div>
<p id="loadingText">Rendering Image!</p>
</div>
</main>
<script src="/qbistListeners.js"></script>
<script src="/qbist.js"></script>

<script src="/qbistListeners.js" type="module"></script>
<script src="/qbist.js" type="module"></script>
<script src="/index.js" type="module"></script>
</body>
</html>
116 changes: 116 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { createInfo, modifyInfo } from "/qbist.js"
import { loadStateFromParam } from "/qbistListeners.js"
function drawQbist(canvas, info, oversampling = 0) {
return new Promise((resolve, reject) => {
if (typeof Worker === 'undefined') {
reject(new Error('Web Workers are not supported in this browser'));
return;
}
const ctx = canvas.getContext("2d")
const width = canvas.width
const height = canvas.height

// Create the worker instance
const worker = new Worker("worker.js", { type: "module" })
// Listen for messages from the worker
worker.addEventListener("message", (e) => {
const { command } = e.data
if (command === "progress") {
const { progress } = e.data
loadingOverlay.style.display = "flex"
loadingBar.style.width = `${progress}%`
} else if (command === "rendered") {
const { imageData } = e.data
const data = new Uint8ClampedArray(imageData)
const imgData = new ImageData(data, width, height)
ctx.putImageData(imgData, 0, 0)
worker.terminate() // Clean up the worker
loadingOverlay.style.display = "none"
resolve() // Resolve the Promise when rendering is complete
}
})

// Listen for errors in the worker
worker.addEventListener("error", (err) => {
worker.terminate() // Clean up the worker on error
loadingOverlay.style.display = "none"
reject(err)
})

// Prepare and send the payload to the worker
const payload = {
command: "render",
info,
width,
height,
oversampling,
}
worker.postMessage(payload)
})
}

// --- Managing the 9-Panel Grid ---
export const formulas = new Array(9)
export const mainFormula = createInfo()

// Generate variations based on the current main formula.
export function generateFormulas() {
formulas[0] = mainFormula
for (let i = 1; i < 9; i++) {
formulas[i] = modifyInfo(mainFormula)
}
}

// Draw the large main pattern and each preview.
export function updateAll() {
const mainCanvas = document.getElementById("mainPattern")
drawQbist(mainCanvas, mainFormula, 1)
for (let i = 0; i < 9; i++) {
const canvas = document.getElementById(`preview${i}`)
drawQbist(canvas, formulas[i], 1)
}
const url = new URL(window.location.href)
url.searchParams.set("state", btoa(JSON.stringify(mainFormula)))
window.history.pushState({}, "", url)
}

// On page load, check if a state is provided in the URL.
function checkURLState() {
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has("state")) {
const state = urlParams.get("state")
loadStateFromParam(state)
return true
}
return false
}

// --- Initialization ---
// Check if a state is provided in the URL and load it.
if (!checkURLState()) {
generateFormulas()
updateAll()
}

export async function downloadImage(outputWidth, outputHeight, oversampling) {
const exportCanvas = document.createElement("canvas")
exportCanvas.width = outputWidth
exportCanvas.height = outputHeight

await drawQbist(exportCanvas, mainFormula, oversampling)

const imageDataURL = exportCanvas.toDataURL("image/png")

// create a temporary download link and trigger the download
const link = document.createElement("a")
link.href = imageDataURL
link.download = "qbist.png"
//TODO: add metadata to the image so that it can be regenerated at another resolution
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}

const loadingOverlay = document.getElementById("loadingOverlay")
const loadingBar = document.getElementById("loadingBar")
loadingOverlay.style.display = "none"
136 changes: 36 additions & 100 deletions qbist.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function randomInt(min, max) {

// --- Formula Generation ---
// Creates a random transformation formula (similar to ExpInfo in C)
function createInfo() {
export function createInfo() {
const info = {
transformSequence: [],
source: [],
Expand All @@ -39,7 +39,7 @@ function createInfo() {
}

// Modify an existing formula by making random changes.
function modifyInfo(oldInfo) {
export function modifyInfo(oldInfo) {
const newInfo = {
transformSequence: oldInfo.transformSequence.slice(),
source: oldInfo.source.slice(),
Expand Down Expand Up @@ -77,7 +77,7 @@ function modifyInfo(oldInfo) {

// --- Optimization ---
// Determines which transformations and registers are actually used.
function optimize(info) {
export function optimize(info) {
const usedTransFlag = new Array(MAX_TRANSFORMS).fill(false)
const usedRegFlag = new Array(NUM_REGISTERS).fill(false)
for (let i = 0; i < MAX_TRANSFORMS; i++) {
Expand Down Expand Up @@ -108,7 +108,7 @@ function optimize(info) {

// --- Core Image Processing (qbist) ---
// Process one pixel using the qbist algorithm.
function qbist(
export function qbist(
info,
x,
y,
Expand Down Expand Up @@ -210,99 +210,35 @@ function qbist(
return [accum[0] / samples, accum[1] / samples, accum[2] / samples]
}

// Draw the pattern on a given canvas using a specific formula.
function drawQbist(canvas, info, oversampling = 0) {
const ctx = canvas.getContext("2d")
const width = canvas.width
const height = canvas.height
const imageData = ctx.createImageData(width, height)
const data = imageData.data
const { usedTransFlag, usedRegFlag } = optimize(info)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const color = qbist(
info,
x,
y,
width,
height,
oversampling,
usedTransFlag,
usedRegFlag
)
const r = Math.floor(color[0] * 255)
const g = Math.floor(color[1] * 255)
const b = Math.floor(color[2] * 255)
const idx = (y * width + x) * 4
data[idx] = r
data[idx + 1] = g
data[idx + 2] = b
data[idx + 3] = 255
}
}
ctx.putImageData(imageData, 0, 0)
}

// --- Managing the 9-Panel Grid ---
let formulas = new Array(9)
let mainFormula = createInfo()

// Generate variations based on the current main formula.
function generateFormulas() {
formulas[0] = mainFormula
for (let i = 1; i < 9; i++) {
formulas[i] = modifyInfo(mainFormula)
}
}

// Draw the large main pattern and each preview.
function updateAll() {
const mainCanvas = document.getElementById("mainPattern")
drawQbist(mainCanvas, mainFormula, 1)
for (let i = 0; i < 9; i++) {
const canvas = document.getElementById("preview" + i)
drawQbist(canvas, formulas[i], 1)
}
const url = new URL(window.location.href)
url.searchParams.set("state", btoa(JSON.stringify(mainFormula)))
window.history.pushState({}, "", url)
}

// On page load, check if a state is provided in the URL.
function checkURLState() {
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has("state")) {
console.log("State found in URL")
const state = urlParams.get("state")
console.log("State:", state)
loadStateFromParam(state)
return true
}
return false
}

// --- Initialization ---
// Check if a state is provided in the URL and load it.
if (!checkURLState()) {
generateFormulas()
updateAll()
}

function downloadImage(outputWidth, outputHeight, oversampling) {
const exportCanvas = document.createElement("canvas")
exportCanvas.width = outputWidth
exportCanvas.height = outputHeight

drawQbist(exportCanvas, mainFormula, oversampling)

const imageDataURL = exportCanvas.toDataURL("image/png")

// create a temporary download link and trigger the download
const link = document.createElement("a")
link.href = imageDataURL
link.download = "qbist.png"
//TODO: add metadata to the image so that it can be regenerated at another resolution
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
// // Draw the pattern on a given canvas using a specific formula.
// function drawQbist(canvas, info, oversampling = 0) {
// const ctx = canvas.getContext("2d")
// const width = canvas.width
// const height = canvas.height
// const imageData = ctx.createImageData(width, height)
// const data = imageData.data
// const { usedTransFlag, usedRegFlag } = optimize(info)
// for (let y = 0; y < height; y++) {
// for (let x = 0; x < width; x++) {
// const color = qbist(
// info,
// x,
// y,
// width,
// height,
// oversampling,
// usedTransFlag,
// usedRegFlag
// )
// const r = Math.floor(color[0] * 255)
// const g = Math.floor(color[1] * 255)
// const b = Math.floor(color[2] * 255)
// const idx = (y * width + x) * 4
// data[idx] = r
// data[idx + 1] = g
// data[idx + 2] = b
// data[idx + 3] = 255
// }
// }
// ctx.putImageData(imageData, 0, 0)
// }
Loading