xxxxxxxxxx
152
//Ridge Hutton
//Instructors: Joel Swanson, David Hunter
//ATLS 4519 - Computational Typography
//ATLAS Institute, University of Colorado Boulder
//this code builds and expands upon a lesson by TheCodingTrains, which can be found here: https://thecodingtrain.com/challenges/166-image-to-ascii
//there is also some code for external webcam usage, which was added in the final hours of working on this. It can be found here: https://editor.p5js.org/codingtrain/sketches/JjRoa1lWO
//For my project I built an interactive video feed that transforms webcam footage into dynamic ASCII art. Users can make changes to the visual aesthetic by adjusting elements such as what characters make up the feed, brightness, and contrast. This customization is driven by the pixel brightness in the video, which determines what characters are picked from a list to create the ASCII art.
let video;
let defaultDensity = "Ñ@#W$9876543210?!abc;:+=-,._ ";
let density = defaultDensity;
let brightnessControl, contrastControl, stepSizeSlider;
const maxStep = 5;
const minFontSize = 18;
const maxFontSize = minFontSize * maxStep;
let stepSize = 1;
function setup() {
//for external webcam
navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(error => {
console.error('Error enumerating devices:', error);
});
createCanvas(windowWidth, windowHeight);
const densityInput = select('#densityInput');
densityInput.input(() => {
density = densityInput.value();
});
const resetButton = select('#resetDensity');
resetButton.mousePressed(() => {
density = defaultDensity;
densityInput.value('');
brightnessControl.value(128);
contrastControl.value(0);
});
brightnessControl = select('#brightnessControl');
brightnessControl.value(128); // Set default brightness
contrastControl = select('#contrastControl');
contrastControl.value(0); // Set default contrast
stepSizeSlider = select('#stepSizeSlider');
stepSizeSlider.input(updateStepSize);
stepSizeSlider.value(stepSize);
}
const devices = [];
function gotDevices(deviceInfos) {
devices.length = 0; // Clear any previous entries
for (let i = 0; i < deviceInfos.length; i++) {
const deviceInfo = deviceInfos[i];
if (deviceInfo.kind === 'videoinput') {
devices.push({
label: deviceInfo.label,
id: deviceInfo.deviceId
});
}
}
console.log(devices);
if (devices.length === 0) {
console.error('No video input devices found.');
return;
}
// Attempt to use the best available device, falling back to defaults
let selectedDeviceId = devices.length > 1 ? devices[1].id : devices[0].id;
var constraints = {
video: {
deviceId: selectedDeviceId,
width: { ideal: 560 }, // Looser width constraint
height: { ideal: 400 }, // Looser height constraint
},
audio: false
};
// Try capturing video with constraints
video = createCapture(constraints, (stream) => {
console.log('Video stream started successfully.');
video.size(175, 50);
video.hide(); // Ensures the video element is hidden
}, (error) => {
console.error('Error starting video stream with constraints:', error);
// Fallback to default video capture with no constraints
video = createCapture(VIDEO);
video.size(175, 50);
video.hide(); // Ensures the video element is hidden
});
// Hide any other video elements that might have been added inadvertently
video.hide();
}
function draw() {
background(0);
if (!video) return;
video.loadPixels();
let brightnessAdjustment = brightnessControl.value() - 128;
let contrastAdjustment = contrastControl.value();
let fontSize = map(stepSize, 1, maxStep, minFontSize, maxFontSize);
textSize(fontSize);
for (let j = 0; j < video.height; j += stepSize) {
for (let i = 0; i < video.width; i += stepSize) {
const pixelIndex = (i + j * video.width) * 4;
let r = video.pixels[pixelIndex + 0] + brightnessAdjustment;
let g = video.pixels[pixelIndex + 1] + brightnessAdjustment;
let b = video.pixels[pixelIndex + 2] + brightnessAdjustment;
r = applyContrast(r, contrastAdjustment);
g = applyContrast(g, contrastAdjustment);
b = applyContrast(b, contrastAdjustment);
const avg = 0.2126 * r + 0.7152 * g + 0.0722 * b;
const len = density.length;
const charIndex = floor(map(avg, 0, 255, len - 1, 0));
const c = density.charAt(charIndex);
fill(r, g, b);
noStroke();
const x = (i / video.width) * width;
const y = (j / video.height) * height;
text(c, x, y);
}
}
}
function applyContrast(color, contrast) {
return 128 + (color - 128) * (contrast / 100 + 1);
}
function updateStepSize() {
stepSize = parseInt(stepSizeSlider.value());
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight - 100);
}