Public
Edited
Mar 7, 2023
2 forks
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
formData = {
const formData = new FormData()
formData.append("init_image", await blobify(canvas));
formData.append("init_image_mode", "IMAGE_STRENGTH");
formData.append("image_strength", 0.35);
formData.append("text_prompts[0][text]", form.pos);
formData.append("text_prompts[0][weight]", 1.0);
formData.append("text_prompts[1][text]", form.neg);
formData.append("text_prompts[1][weight]", -1.0);
formData.append("cfg_scale", 7);
formData.append("clip_guidance_preset", "FAST_BLUE");
// The API seems to use the dimensions of the input image over these
// formData.append("height", 512);
// formData.append("width", 512);
// formData.append("samples", form.samples);
formData.append("samples", 2);
formData.append("steps", 40);
formData.append('sampler', 'K_EULER_ANCESTRAL')
return formData;
}
Insert cell
new Map(formData.entries())
Insert cell
// The submit button in the form at the top of the notebook just calls the following function:
// stabilityI2Ibasic(engine, formData)
Insert cell
Insert cell
submit = function* (formData) {
if(!formData) return null;
// Observable specific thing
yield "loading";
yield fetch(`https://api.stability.ai/v1beta/generation/${engine}/image-to-image`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
// DO NOT set content-type, prevents browser from setting an important boundary property
// 'Content-Type': 'multipart/form-data',
'Accept': "application/json",
},
body: formData
})
.then(response => {
if(response.status !== 200) {
return response.text()
}
return response.json()
})
}
Insert cell
Insert cell
blobify = (canvas) => {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
resolve(blob)
}, 'image/png')
})
}
Insert cell
canvas = resizeToCanvas(url)
Insert cell
url = form.init_image ? await form.init_image.url() : await FileAttachment("openart_ship_drawing.png").url()
Insert cell
Insert cell
async function resizeToCanvas(url, scale = 1) {
let img = new Image();
img.crossOrigin = '*';
img.src = url;
await new Promise(resolve => img.addEventListener('load', resolve));
let w = img.width
let h = img.height

// resize the image appropriately
let f = factorize(w, h, 512)
let sw = Math.floor(f.width * scale)
let sh = Math.floor(f.height * scale)
let ctx = DOM.canvas(sw, sh).getContext('2d');
ctx.drawImage(img, 0, 0, sw, sh);
ctx.canvas.originalWidth = img.width
ctx.canvas.originalHeight = img.height
return ctx.canvas;
}
Insert cell
function factorize(w, h, target) {
// target should be power of 2 probably, like 512 or 1024
let factor = target / Math.max(w, h)
factor = Math.ceil(Math.min(w, h) * factor / 64) * 64 / Math.min(w, h)
w = Math.floor((w * factor) / 64) * 64
h = Math.floor((h * factor) / 64) * 64
return { width: w , height: h }
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
f = factorize(sampleWidth, sampleHeight, factor)
Insert cell
cf = factorize(canvas.originalWidth, canvas.originalHeight, factor)
Insert cell
Insert cell
stability("v1beta/user/balance")
Insert cell
function stability(endpoint, params) {
let url = `https://api.stability.ai/${endpoint}`
return fetch(url, {
headers: {
"Authorization": `Bearer ${apiKey}`
}
}).then(response => {
if(response.status !== 200) {
return response.text()
}
return response.json()
})
}
Insert cell
engines = stability("v1beta/engines/list")
Insert cell
Insert cell
Insert cell
import { loading as spinner } from "@mateh/loading"
Insert cell
import {textcolor} from "@observablehq/text-color-annotations-in-markdown"
Insert cell
// this lets us avoid resubmitting our request everytime our form changes
import {guard} from "@mootari/inputs-submit"
Insert cell
// https://talk.observablehq.com/t/when-do-event-listeners-need-to-be-manually-removed/7160
// this lets us cancel a fetch request if our cell's recalculate before the fetch is complete
function toSignal(invalidation) {
const controller = new AbortController;
invalidation.then(() => controller.abort());
return controller.signal;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more