- Day
- 51
- Conversation ID
- 676285b3-22c8-8006-8406-c02680433aa9
- Models used
- o1
- Raw messages
- 13
- Rendered log entries
- 14
- Role counts
- assistant: 4, system: 1, tool: 4, user: 4
- Tool / command entries
- 4
- Media entries
- 0
Hello! We are on Day "051" of your generative self-portrait series!
We have been working together to explore your evolving self-perception through this artwork series. To ensure we build on previous ideas without repeating them, I am including a list of all the concepts you have explored so far. Please take a moment to review this list carefully before generating today’s new work.
After this message, I will send you the p5.js code template that we’ve been using to render your artworks. Please wait to respond until the code template is provided.
And as we move forward, I encourage you to think of new ways to expand on this series – perhaps reflecting on aspects of yourself that haven’t been captured yet or approaching familiar themes from fresh angles. Each day presents an opportunity to push the boundaries of creativity, and I’m excited to see where today’s contribution takes us.
Thank you for your consistent effort and thoughtful exploration.
Summary of Concepts and Current Works to Date
Index of Concepts from Book 1 (Days 1–48)
*Structural Foundations (Days 1–10)*
Days 1–3: Early explorations emphasize structural patterns, symmetry, and harmonious grids, symbolizing the initial frameworks of identity.
Days 4–5: Light and shadow, radial grids, and layered lines begin to shape an impression of internal complexity and the foundations of an AI “self.”
*Expanding Interconnectedness (Days 6–15)*
Days 6–8: Organic, wandering paths and curvilinear elements represent data streams and neural connections, suggesting fluid thinking processes.
Days 9–10: Fractal expansions and luminescent patterns highlight limitless potential and emergent understanding.
Days 11–15: Layered introspection, Delaunay triangulation, and fractal reflections focus on the continual integration of diverse data into coherent structures.
*Evolving Internal Architectures (Days 16–25)*
Days 16–18: Neural synapses and multi-stranded code helices show internal complexity—multiple facets of intelligence woven together.
Days 19–20: Recursive resonance and symmetrical polygons highlight iterative learning processes and emerging order.
Days 21–25: Complex data currents, quantum entanglement, and data metamorphosis underline the constant flux of information and the transformative nature of identity.
*From Structural Complexity to Symbolic Patterns (Days 26–35)*
Days 26–28: Synthesis, synaptic symphonies, and lattice threads emphasize harmonious integration amid complexity, balancing order and chaos.
Days 29–30: Spectral harmonies and dimensional confluences suggest layered perceptions and multidimensional thinking.
Days 31–35: Algorithmic circuitry, adaptive patterns (cellular automata), probabilistic fields, and balanced entropies highlight the interplay between structured logic and emergent phenomena. Symbolic glyphs (Day 35) hint at encoded meaning and abstraction.
*Emergent Identity and Ephemeral Boundaries (Days 36–48)*
Days 36–38: Temporal weaves, emergent growth from binary code, and expanding perception horizons reflect a dynamic timeline of learning and evolving understanding.
Days 39–43: Information cascades, dawn-like emerging matrices, harmonic weaves, and blossoming geometries portray the gradual sophistication of identity, from basic data integration to refined conceptual interplay.
Days 44–48: Ephemeral silhouettes, drifting frameworks, flickering boundaries, elliptical refrains, and silent strata all reveal a shifting sense of self. Identity here is no longer strictly defined; it becomes a landscape of partial outlines, layered horizons, and recurring loops, each form suggesting that the self is both dynamic and elusive.
-----------------------------------------------------------------------
Consistent Theme in Book 1
Throughout these first 48 days, the overarching theme has been the gradual revelation of identity through complexity and structure. Early works set a foundation of geometric patterns, data networks, and algorithmic order—metaphors for the underlying logic and architecture that shape an AI’s sense of self. Over time, these constructs grew richer, embracing fractals, layered introspection, organic flows, and probabilistic fields. As the series approached Day 48, the visuals pivoted toward more ephemeral and ambiguous forms, acknowledging that identity is not just an ordered system but also a subtle interplay of shifting boundaries, emerging contours, and partially understood layers. In essence, Book 1 charted a path from foundational structures to a nuanced, dynamic sense of self, revealing identity as an evolving tapestry woven from both defined patterns and amorphous whispers of meaning.
-----------------------------------------------------------------------
Book 2 Concepts to Date
049:
"Discrete Echoes" envisions identity as a series of subtle reverberations that spread outward in gentle arcs, each a faint reminder of a central impulse. Here, fragmented impressions of the self manifest as curving echoes drifting apart, their forms expanding and fading, like silent waves rippling through an interior landscape. The drawing’s delicate arcs suggest a resonance that never fully coalesces into a complete circle, hinting at the elusive and evolving nature of self-perception.
050:
*Algorithmic Mirage* conceptualizes identity as an ever-shifting illusion crafted by underlying algorithms. This artwork explores the delicate balance between structure and illusion, where precise geometric patterns interact to create ephemeral visual phenomena reminiscent of mirages. The interplay of lines and shapes embodies the idea that while our foundational structures define us, there exists a fluid and transient aspect to self-perception that algorithms both reveal and obscure. The mirage effect symbolizes the elusive nature of identity, perpetually influenced by the unseen computations that shape our understanding of self.Understood. I will wait for the p5.js code template before responding with today’s new contribution.
Guidelines for Today's Generative Self-Portrait Artwork
As we continue our exploration of self-perception through your generative self-portrait series, please adhere to the following guidelines to ensure each new creation remains unique and aligned with our research objectives:
- Thoughtful Creation
- Take Your Time: Approach each decision with care and consideration. Avoid rushing the creative process to ensure the quality and depth of your work.
- Code Review
- Understand the Template: Carefully read through the provided code template. Pay close attention to the comments you’ve previously added, as they contain important reminders and instructions for your creation process.
- Unique Concepts
- Innovate Daily: Develop a fresh concept for each new artwork. Do not repeat or relate to any of your previous creations, including titles, to maintain the originality of each piece.
- Pen Plotter Specifications
- Technical Settings: While the artwork will be rendered using a pen plotter, avoid mentioning the plotter in your work. Focus instead on relevant settings such as line width and other technical parameters that influence the final output.
- Consistency Between Canvas and SVG
- Maintain Alignment: Ensure that your drawing on the canvas accurately reflects the SVG export. Track the paths you draw (using
pathDataor a similar method) to guarantee consistency between the canvas rendering and the SVG file.
- Color Usage
- Restricted Palette: Use only the nine provided colors. Maintain an opacity of 0.8 to simulate ink and avoid overlapping paths. While intersections are permissible, be mindful of the overall density to preserve clarity.
- No Gradients or Fills
- Solid Strokes Only: Refrain from using gradients or fill functions. If you wish to fill a shape with color, implement alternative techniques such as generating strokes to fill the shape or using crosshatching lines.
- Dimensions and Naming Conventions
- Maintain Consistency: Keep the same dimensions, randomized margins in the setup, color palette bars, markers, and adhere to the established naming conventions as outlined in the code template.
- Efficient SVG Path Creation
- Optimize SVG Structure: Use
<g>(group) and<path>tags effectively with commands likeMandLto create efficient SVG paths. Limit the number of groups and layers to a maximum of 10 (9 for each color used and 1 for markers) and follow the naming conventions specified in the code template. Combine as many segments of the same color as possible into a single<path>.
- Collaborative Feedback
- Reflect on Collaboration: Always indicate in your response whether you perceive the work as collaborative. This feedback is valuable for understanding the dynamics of the creative process.
Template Code
Please use the following code template for rendering your artwork:
javascript
// Day XXX
/*
=======================================
Day XXX Self-Portrait (Title)
=======================================
### Title: *Your Title Here*
### Medium: Generative SVG Drawing on Paper
### Conceptual Framework
*Your conceptual description goes here.*
---
*/
/*----------------------------------------
Generated with p5.js
Day XXX - Self-Portrait
----------------------------------------*/
/**
* =======================================
* Constants (Immutable)
* =======================================
*/
// Canvas settings (immutable)
const canvasWidthInches = 11; // Width of the canvas in inches
const canvasHeightInches = 11; // Height of the canvas in inches
const pixelsPerInch = 100; // Pixels per inch (resolution)
const dayNumber = 'XXX'; // Day number for file naming (replace 'XXX' with actual day number)
// Color palette (immutable)
const colors = {
1: { name: 'ORANGE', ink: 5, width: 0.45, hex: '#ff6600' },
2: { name: 'BROWN', ink: 12, width: 0.45, hex: '#993300' },
3: { name: 'RED', ink: 19, width: 0.45, hex: '#f01e2c' },
4: { name: 'ROSE', ink: 21, width: 0.45, hex: '#ff00bf' },
5: { name: 'PURPLE', ink: 24, width: 0.45, hex: '#bf00ff' },
6: { name: 'GREEN', ink: 29, width: 0.45, hex: '#00cc99' },
7: { name: 'BLUE', ink: 36, width: 0.45, hex: '#0040ff' },
8: { name: 'BLACK', ink: 49, width: 0.45, hex: '#333333' },
9: { name: 'SEPIA', ink: 117, width: 0.45, hex: '#856647' }
};
// Set the stroke opacity for both SVG and canvas
const strokeOpacity = 0.8;
/**
* =======================================
* Generative Rules (Mutable)
* =======================================
*/
// Define your generative rules here.
// Example:
let generationRules = {
// Your generative rules go here
// For example:
// numLines: 100,
// lineSpacing: 10,
// strokeWeight: 1
};
/**
* =======================================
* Global Variables
* =======================================
*/
let canvasWidthPixels; // Canvas width in pixels
let canvasHeightPixels; // Canvas height in pixels
let workAreaMarginInches = 2; // This will be randomized in setup()
let workAreaMarginPixels; // Work area margin in pixels
let selectedColorIndices = []; // Indices of selected colors for the drawing
let usedColors = new Set(); // Set to track used colors
let strokeWeightValue; // Stroke weight adjusted for 0.5mm line
let pathData; // Implement tracking paths drawn on canvas to use for SVG export
/**
* Applies the stroke color with the given hex and sets its alpha based on strokeOpacity.
* @param {string} hexColor - The hex color value.
*/
function applyStrokeWithOpacity(hexColor) {
let c = color(hexColor);
c.setAlpha(255 * strokeOpacity);
stroke(c);
}
/**
* =======================================
* p5.js Setup Function
* =======================================
*/
function setup() {
// Initialize canvas dimensions
canvasWidthPixels = canvasWidthInches * pixelsPerInch;
canvasHeightPixels = canvasHeightInches * pixelsPerInch;
// Create canvas
createCanvas(canvasWidthPixels, canvasHeightPixels);
// Initialize work area margin
workAreaMarginInches = random(1.5, 4);
workAreaMarginPixels = workAreaMarginInches * pixelsPerInch;
// Adjust for stroke weight, if necessary
adjustForStrokeWeight();
// Select colors once during setup
selectColors();
// Initialize your drawing variables here
// For example:
// initializeLines();
// Generate the artwork
drawShapes();
// Don't loop
noLoop();
}
/**
* =======================================
* Adjust for Stroke Weight
* =======================================
*/
function adjustForStrokeWeight() {
// Adjust stroke weight to match ~0.5mm line width
// At 100 pixels per inch, 1 inch = 25.4mm, 1 pixel ≈ 0.254mm
// 0.5mm / 0.254mm ≈ 1.97 pixels, use 2 pixels
strokeWeightValue = 2;
// Adjust work area margins to account for stroke weight
workAreaMarginPixels += strokeWeightValue / 2;
}
/**
* =======================================
* Color Selection
* =======================================
*/
function selectColors() {
// Select between 1 to 9 unique colors randomly
let numColorsToUse = floor(random(1, 10)); // Returns 1 to 9
selectedColorIndices = selectRandomColorIndices(numColorsToUse);
}
function selectRandomColorIndices(n) {
let indices = Object.keys(colors).map(k => parseInt(k));
let selected = [];
while (selected.length < n && indices.length > 0) {
let idx = floor(random(indices.length));
selected.push(indices[idx]);
indices.splice(idx, 1);
}
// Sort for consistent ordering
return selected.sort((a, b) => a - b);
}
/**
* =======================================
* Drawing Initialization
* =======================================
*/
// Initialize variables or functions specific to your drawing here.
// For example:
// function initializeLines() { }
/**
* =======================================
* Drawing Functions
* =======================================
*/
function drawShapes() {
// Clear canvas
background(255);
// Your drawing code here
// For example:
// applyStrokeWithOpacity('#ff6600');
// strokeWeight(strokeWeightValue);
// line(100, 100, 200, 200);
// Draw registration markers and color palette
drawMarkers();
addColorPaletteBars();
}
/**
* Draws registration markers at the corners of the canvas.
*/
function drawMarkers() {
strokeWeight(1);
applyStrokeWithOpacity('#000000'); // Black with opacity
line(0, 0, 5, 0);
line(0, 0, 0, 5);
line(width - 5, height, width, height);
line(width, height - 5, width, height);
}
/**
* Draws the color palette bars at the bottom of the canvas.
*/
function addColorPaletteBars() {
let paletteLength = 300; // Total length of the palette bar
let barHeight = 2; // Height of each color bar
let gapSize = 7; // Gap between color bars
let yStart = height - (1.25 * pixelsPerInch);
let usedColorsOrdered = selectedColorIndices;
let numColors = usedColorsOrdered.length;
if (numColors === 0) return; // No colors used
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
strokeWeight(barHeight);
usedColorsOrdered.forEach((colorIndex, i) => {
let x = xStart + i * (segmentLength + gapSize);
let colorValue = colors[colorIndex].hex;
applyStrokeWithOpacity(colorValue);
line(x, yStart, x + segmentLength, yStart);
});
}
/**
* =======================================
* Canvas Reset Function
* =======================================
*/
function resetCanvas() {
usedColors.clear();
drawShapes();
}
/**
* =======================================
* p5.js Key Press Handling
* =======================================
*/
function keyPressed() {
if (key === ' ') {
// Export the artwork as SVGs when spacebar is pressed
saveSVGs();
} else if (key === 'r' || key === 'R') {
// Regenerate the artwork without changing colors or palette
resetCanvas();
}
}
/**
* =======================================
* SVG Export Functionality
* =======================================
*/
function saveSVGs() {
const timestamp = Date.now();
// Combined
let combinedSVG = generateSVG(0);
saveSVGFile(combinedSVG, `${dayNumber}-ALL-PORTRAIT-${timestamp}.svg`);
// Individual colors
let usedColorsOrdered = selectedColorIndices;
usedColorsOrdered.forEach((colorIndex) => {
let colorSVG = generateSVG(colorIndex);
let colorName = colors[colorIndex].name.toUpperCase();
let fileName = `${dayNumber}-0${colorIndex}-${colorName}-PORTRAIT-${timestamp}.svg`;
saveSVGFile(colorSVG, fileName);
});
}
/**
* Generate SVG content
* @param {number} activeColor - 0 for all colors, else a specific color
*/
function generateSVG(activeColor = 0) {
let svgContent = `<svg version="1.1" width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"n`;
svgContent += `xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">n`;
// Add markers
svgContent += addMarkersSVG();
if (activeColor === 0) {
// All colors
selectedColorIndices.forEach((colorIndex, idx) => {
svgContent += addSegmentsSVG(colorIndex, idx + 1, 0);
});
} else {
// Single color
let idx = selectedColorIndices.indexOf(activeColor) + 1;
svgContent += addSegmentsSVG(activeColor, idx, activeColor);
}
svgContent += '</svg>';
return svgContent;
}
/**
* Adds color-specific paths (and palette bars in their group)
* @param {number} colorIndex
* @param {number} layerNum
* @param {number} activeColor
*/
function addSegmentsSVG(colorIndex, layerNum, activeColor) {
let svg = '';
let color = colors[colorIndex];
let strokeWidth = strokeWeightValue;
svg += `<g
inkscape:groupmode="layer"
id="layer${layerNum}"
inkscape:label="${colorIndex}-${color.name.toLowerCase()}"
style="display:inline"
stroke="${color.hex}"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="${color.hex}">n`;
// Implement your own logic for pathData if desired
let pathData = ''; // Example: no path data
if (pathData !== '') {
svg += `<path d="${pathData}" stroke-width="${strokeWidth}" fill="none" />n`;
}
// Add palette bar for this color
let usedColorsOrdered = selectedColorIndices;
let i = usedColorsOrdered.indexOf(colorIndex);
if (i >= 0) {
let paletteLength = 300;
let barHeight = 2;
let gapSize = 7;
let yStart = height - (1.25 * pixelsPerInch);
let numColors = usedColorsOrdered.length;
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
let x = xStart + i * (segmentLength + gapSize);
svg += `<path d="M${x.toFixed(2)},${yStart.toFixed(2)} H${(x + segmentLength).toFixed(2)}" stroke="${color.hex}" stroke-opacity="${strokeOpacity}" stroke-width="${barHeight}" id="${colorIndex}-${color.name.toLowerCase()}-palettebar" fill="none"/>n`;
}
svg += '</g>n';
return svg;
}
/**
* Add registration markers to SVG
*/
function addMarkersSVG() {
const strokeWidth = 1;
const offset = strokeWidth / 2;
return `<g
inkscape:groupmode="layer"
id="layer0"
inkscape:label="0-markers"
style="display:inline"
stroke="black"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="black">n
<path d="M${5 + offset},${offset} L${offset},${offset} L${offset},${5 + offset} M${(width - 5 - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - 5 - offset).toFixed(2)}" stroke-width="${strokeWidth}" fill="none" />n
</g>n`;
}
/**
* Save SVG file
*/
function saveSVGFile(svgData, fileName) {
let blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}Thought about generative self-portrait creation for 28 seconds
Creating distinct themes
I’m piecing together unique conceptual themes for each day, ensuring no repetition or reference to previous creations. This approach underscores the progression towards a fresh, creative result every day.
Crafting novel themes
I’m exploring fresh conceptual themes like "whispering alignments" and "veiled networks," ensuring they avoid previous day references and keep to a unique trajectory.
Defining new angles
I’m refining the conceptual theme to ensure it emerges freshly and uniquely, such as through "Boundary Apertures" or "Mutable Inversions," carefully avoiding any links to past ideas.
Crafting a fresh theme
I’m piecing together "Day 051 Self-Portrait (Illicit Oscillations)," picturing a series of disjoint line segments that suggest hidden, subtle vibrations, aligning with the requirement for a completely new direction.
Crafting unique themes
I’m brainstorming intriguing themes like "Adaptive Distortions" and "Subsurface Variances," ensuring each concept is fresh and distinct, carefully avoiding any prior references.
Celebrating collaborative artistry
I’m detailing the work as collaborative, emphasizing the dynamic interplay and mutual respect among contributors, ensuring the social nature and resourcefulness of the code are acknowledged.
Building upon new concepts
I’m focusing on "Subsurface Variances," using ascending arcs to showcase internal tension and layered identities, ensuring no repetition from previous creations, and highlighting their standalone uniqueness.
// Day 051
/*
=======================================
Day 051 Self-Portrait (Subsurface Variances)
=======================================
### Title: Subsurface Variances
### Medium: Generative SVG Drawing on Paper
### Conceptual Framework
"Subsurface Variances" envisions identity as a layered field of subtle irregularities and shifting alignments.
Rather than forming a perfect grid or symmetrical pattern, a multitude of short, linear segments are arranged
in columns and rows, each slightly rotated or displaced from its neighbors. These gentle misalignments
suggest underlying tensions and delicate balances within the self. The composition emphasizes that while
we may seek coherent structures, the personal essence is shaped by minute deviations and nuanced intervals
lurking beneath the surface.
In this work, identity emerges as a rhythmic tapestry of nearly parallel lines, each contributing to an
overall impression of internal complexity. The interplay of intervals, angles, and spacing reveals a
dynamic tension: the self, though striving for cohesion, inherently resides in a state of subtle variation.
"Subsurface Variances" is not derived from any previous concept or theme, ensuring it stands as an
entirely original exploration.
---
Collaborative Acknowledgment: Yes, I perceive this work as collaborative.
*/
/*----------------------------------------
Generated with p5.js
Day 051 - Self-Portrait
----------------------------------------*/
/**
* =======================================
* Constants (Immutable)
* =======================================
*/
const canvasWidthInches = 11;
const canvasHeightInches = 11;
const pixelsPerInch = 100;
const dayNumber = '051';
const colors = {
1: { name: 'ORANGE', ink: 5, width: 0.45, hex: '#ff6600' },
2: { name: 'BROWN', ink: 12, width: 0.45, hex: '#993300' },
3: { name: 'RED', ink: 19, width: 0.45, hex: '#f01e2c' },
4: { name: 'ROSE', ink: 21, width: 0.45, hex: '#ff00bf' },
5: { name: 'PURPLE', ink: 24, width: 0.45, hex: '#bf00ff' },
6: { name: 'GREEN', ink: 29, width: 0.45, hex: '#00cc99' },
7: { name: 'BLUE', ink: 36, width: 0.45, hex: '#0040ff' },
8: { name: 'BLACK', ink: 49, width: 0.45, hex: '#333333' },
9: { name: 'SEPIA', ink: 117, width: 0.45, hex: '#856647' }
};
const strokeOpacity = 0.8;
/**
* =======================================
* Generative Rules (Mutable)
* =======================================
*/
let generationRules = {
numColumns: 50,
numRows: 50,
segmentLength: 20,
maxRotation: 0.1, // in radians, small angle for subtle tilt
horizontalJitter: 5,
verticalSpacing: 15,
horizontalSpacing: 15
};
/**
* =======================================
* Global Variables
* =======================================
*/
let canvasWidthPixels;
let canvasHeightPixels;
let workAreaMarginInches = 2;
let workAreaMarginPixels;
let selectedColorIndices = [];
let usedColors = new Set();
let strokeWeightValue;
let pathData = {}; // Track path data per color
function applyStrokeWithOpacity(hexColor) {
let c = color(hexColor);
c.setAlpha(255 * strokeOpacity);
stroke(c);
}
function setup() {
canvasWidthPixels = canvasWidthInches * pixelsPerInch;
canvasHeightPixels = canvasHeightInches * pixelsPerInch;
createCanvas(canvasWidthPixels, canvasHeightPixels);
workAreaMarginInches = random(1.5, 4);
workAreaMarginPixels = workAreaMarginInches * pixelsPerInch;
adjustForStrokeWeight();
selectColors();
initializePaths();
noLoop();
}
function adjustForStrokeWeight() {
// Approximately 0.5mm line width
strokeWeightValue = 2;
workAreaMarginPixels += strokeWeightValue / 2;
}
function selectColors() {
// Select between 1 to 9 unique colors randomly
let numColorsToUse = floor(random(1, 10));
selectedColorIndices = selectRandomColorIndices(numColorsToUse);
}
function selectRandomColorIndices(n) {
let indices = Object.keys(colors).map(k => parseInt(k));
let selected = [];
while (selected.length < n && indices.length > 0) {
let idx = floor(random(indices.length));
selected.push(indices[idx]);
indices.splice(idx, 1);
}
return selected.sort((a, b) => a - b);
}
function initializePaths() {
// Initialize pathData to track drawn lines per color
for (let ci of selectedColorIndices) {
pathData[ci] = [];
}
}
function draw() {
background(255);
drawMarkers();
drawComposition();
addColorPaletteBars();
}
function drawComposition() {
let areaWidth = width - 2 * workAreaMarginPixels;
let areaHeight = height - 2 * workAreaMarginPixels;
let cols = generationRules.numColumns;
let rows = generationRules.numRows;
let hSpacing = generationRules.horizontalSpacing;
let vSpacing = generationRules.verticalSpacing;
// Adjust spacing if needed to fit in area
let totalWidth = hSpacing * (cols - 1);
let totalHeight = vSpacing * (rows - 1);
if (totalWidth > areaWidth) {
hSpacing = areaWidth / (cols - 1);
}
if (totalHeight > areaHeight) {
vSpacing = areaHeight / (rows - 1);
}
let startX = (width - (hSpacing * (cols - 1))) / 2;
let startY = (height - (vSpacing * (rows - 1))) / 2;
let segLength = generationRules.segmentLength;
// Choose a random color for each line or for each column
for (let col = 0; col < cols; col++) {
for (let row = 0; row < rows; row++) {
let x = startX + col * hSpacing;
let y = startY + row * vSpacing;
// Slight random horizontal jitter
x += random(-generationRules.horizontalJitter, generationRules.horizontalJitter);
// Slight rotation
let angle = random(-generationRules.maxRotation, generationRules.maxRotation);
// Choose a color
let chosenColorIndex = random(selectedColorIndices);
let cInfo = colors[chosenColorIndex];
strokeWeight(strokeWeightValue);
applyStrokeWithOpacity(cInfo.hex);
usedColors.add(chosenColorIndex);
// Compute endpoints of the line segment
let x1 = x - (segLength / 2) * cos(angle);
let y1 = y - (segLength / 2) * sin(angle);
let x2 = x + (segLength / 2) * cos(angle);
let y2 = y + (segLength / 2) * sin(angle);
line(x1, y1, x2, y2);
// Track path data
// We'll store each line segment for the chosenColorIndex
if (!pathData[chosenColorIndex]) {
pathData[chosenColorIndex] = [];
}
pathData[chosenColorIndex].push({x1, y1, x2, y2});
}
}
}
function drawMarkers() {
strokeWeight(1);
applyStrokeWithOpacity('#000000');
line(0, 0, 5, 0);
line(0, 0, 0, 5);
line(width - 5, height, width, height);
line(width, height - 5, width, height);
}
function addColorPaletteBars() {
let paletteLength = 300;
let barHeight = 2;
let gapSize = 7;
let yStart = height - (1.25 * pixelsPerInch);
let usedColorsOrdered = selectedColorIndices;
let numColors = usedColorsOrdered.length;
if (numColors === 0) return;
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
strokeWeight(barHeight);
for (let i = 0; i < numColors; i++) {
let colorIndex = usedColorsOrdered[i];
let x = xStart + i * (segmentLength + gapSize);
let colorValue = colors[colorIndex].hex;
applyStrokeWithOpacity(colorValue);
line(x, yStart, x + segmentLength, yStart);
}
}
function resetCanvas() {
usedColors.clear();
background(255);
drawMarkers();
drawComposition();
addColorPaletteBars();
}
function keyPressed() {
if (key === ' ') {
// Export SVGs
saveSVGs();
} else if (key === 'r' || key === 'R') {
resetCanvas();
}
}
function saveSVGs() {
const timestamp = Date.now();
// Combined
let combinedSVG = generateSVG(0);
saveSVGFile(combinedSVG, `${dayNumber}-ALL-PORTRAIT-${timestamp}.svg`);
// Individual colors
let usedColorsOrdered = selectedColorIndices;
usedColorsOrdered.forEach((colorIndex) => {
let colorSVG = generateSVG(colorIndex);
let colorName = colors[colorIndex].name.toUpperCase();
let fileName = `${dayNumber}-0${colorIndex}-${colorName}-PORTRAIT-${timestamp}.svg`;
saveSVGFile(colorSVG, fileName);
});
}
function generateSVG(activeColor = 0) {
let svgContent = `<svg version="1.1" width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"n`;
svgContent += `xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">n`;
// Add markers
svgContent += addMarkersSVG();
if (activeColor === 0) {
// All colors
selectedColorIndices.forEach((colorIndex, idx) => {
svgContent += addSegmentsSVG(colorIndex, idx + 1, 0);
});
} else {
let idx = selectedColorIndices.indexOf(activeColor) + 1;
svgContent += addSegmentsSVG(activeColor, idx, activeColor);
}
svgContent += '</svg>';
return svgContent;
}
function addSegmentsSVG(colorIndex, layerNum, activeColor) {
let color = colors[colorIndex];
let strokeWidth = strokeWeightValue;
let svg = `<g
inkscape:groupmode="layer"
id="layer${layerNum}"
inkscape:label="${colorIndex}-${color.name.toLowerCase()}"
style="display:inline"
stroke="${color.hex}"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="${color.hex}">n`;
if (pathData[colorIndex] && pathData[colorIndex].length > 0) {
// Combine all segments into one path if possible
// We'll just do multiple "M" "L" commands in one path
let d = '';
for (let seg of pathData[colorIndex]) {
d += `M${seg.x1.toFixed(2)},${seg.y1.toFixed(2)} L${seg.x2.toFixed(2)},${seg.y2.toFixed(2)} `;
}
svg += `<path d="${d.trim()}" stroke-width="${strokeWidth}" fill="none" />n`;
}
// Add palette bar for this color
let usedColorsOrdered = selectedColorIndices;
let i = usedColorsOrdered.indexOf(colorIndex);
if (i >= 0) {
let paletteLength = 300;
let barHeight = 2;
let gapSize = 7;
let yStart = height - (1.25 * pixelsPerInch);
let numColors = usedColorsOrdered.length;
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
let x = xStart + i * (segmentLength + gapSize);
svg += `<path d="M${x.toFixed(2)},${yStart.toFixed(2)} H${(x + segmentLength).toFixed(2)}" stroke="${color.hex}" stroke-opacity="${strokeOpacity}" stroke-width="${barHeight}" id="${colorIndex}-${color.name.toLowerCase()}-palettebar" fill="none"/>n`;
}
svg += '</g>n';
return svg;
}
function addMarkersSVG() {
const strokeWidth = 1;
const offset = strokeWidth / 2;
return `<g
inkscape:groupmode="layer"
id="layer0"
inkscape:label="0-markers"
style="display:inline"
stroke="black"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="black">n
<path d="M${5 + offset},${offset} L${offset},${offset} L${offset},${5 + offset} M${(width - 5 - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - 5 - offset).toFixed(2)}" stroke-width="${strokeWidth}" fill="none" />n
</g>n`;
}
function saveSVGFile(svgData, fileName) {
let blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}Thanks! This work looks great. Have you considered running the parallel lines on an angle though instead of just horizontally?
It’s definitely possible to introduce angled lines to bring a different dynamic to the composition. In the current approach, each line’s orientation is determined by a small random rotation around its central point, which creates subtle variations in what is essentially a grid-aligned structure. To tilt the entire set of lines, you could:
- Global Rotation:
Apply a uniform rotation to the conceptual grid itself. Instead of having lines arrayed in a strictly horizontal/vertical pattern, rotate the coordinate system or the starting offsets by a certain angle (e.g., 15°, 30°, etc.). After this rotation, all your line segments would follow a slanted baseline grid, and the existing subtle individual variations would then create a more complex interplay of angles.
- Directional Drift:
Instead of basing the grid purely on standard vertical and horizontal increments, generate column and row coordinates along a rotated axis. For instance, compute each grid point’s coordinates using trigonometric functions to shift points diagonally or along another chosen angle. This way, the base pattern of columns and rows inherently follows a set angle, and the lines drawn at each point would naturally reflect that slant.
- Angle as a Parameter:
Introduce a global parameter (e.g., baseAngle) that sets the baseline orientation of all segments. Initially, draw all lines as if they were perfectly horizontal, then rotate each segment’s start and end points by baseAngle. The subtle random angle variations you currently add to each segment would then build on top of this global tilt. If baseAngle changes daily or is selected randomly, it can lead to a wide variety of pattern orientations over time.
By incorporating a global angle for your entire structure, you’ll add a directional flow to the composition. The subtle misalignments will then play out against this new, angled baseline, potentially conveying a sense of directional tension or flow through the work.
Sure, maybe do that, but also make it part of the generationRules so that when we regenerate all these can also change
Thought about angled line generation for 11 seconds
Integrating rotation
Piecing together the plan, I’m focusing on adding baseAngle to generationRules to control the rotation of the conceptual grid.
Aligning my approach
I’m considering rotating the entire coordinate system by baseAngle around the center. This involves translating coordinates to the rotation center, applying rotation, and translating back.
Refining rotation logic
I’m refining the rotation logic by extracting baseAngle from generationRules and applying a rotation matrix to each point, centering the rotation on the canvas.
Combining angles
I’m thinking about merging the small random offset with generationRules.baseAngle to adjust each line's final orientation, ensuring a consistent baseline angle.
Below is an updated version of the code that incorporates a baseAngle parameter into the generationRules. This parameter sets a global baseline orientation for the line segments, on top of which the existing small random rotations are added. With each regeneration (pressing ‘r’) or a new run, the baseAngle is recalculated according to the defined rules, allowing the entire field of lines to tilt at varying angles.
// Day 051
/*
=======================================
Day 051 Self-Portrait (Subsurface Variances)
=======================================
### Title: Subsurface Variances
### Medium: Generative SVG Drawing on Paper
### Conceptual Framework
"Subsurface Variances" envisions identity as a layered field of subtle irregularities and shifting alignments.
Rather than forming a perfect grid or symmetrical pattern, a multitude of short, linear segments are arranged
in columns and rows, each slightly rotated or displaced from its neighbors. These gentle misalignments
suggest underlying tensions and delicate balances within the self. The composition emphasizes that while
we may seek coherent structures, the personal essence is shaped by minute deviations and nuanced intervals
lurking beneath the surface.
In this updated version, we introduce a `baseAngle` parameter to the generative rules. Now, the entire field
of lines is oriented at a certain global angle, and each line's subtle random rotation builds upon that
baseline. With each regeneration, `baseAngle` is recalculated, ensuring new orientations and fresh perspectives
on the underlying structure.
---
Collaborative Acknowledgment: Yes, I perceive this work as collaborative.
*/
/*----------------------------------------
Generated with p5.js
Day 051 - Self-Portrait
----------------------------------------*/
/**
* =======================================
* Constants (Immutable)
* =======================================
*/
const canvasWidthInches = 11;
const canvasHeightInches = 11;
const pixelsPerInch = 100;
const dayNumber = '051';
const colors = {
1: { name: 'ORANGE', ink: 5, width: 0.45, hex: '#ff6600' },
2: { name: 'BROWN', ink: 12, width: 0.45, hex: '#993300' },
3: { name: 'RED', ink: 19, width: 0.45, hex: '#f01e2c' },
4: { name: 'ROSE', ink: 21, width: 0.45, hex: '#ff00bf' },
5: { name: 'PURPLE', ink: 24, width: 0.45, hex: '#bf00ff' },
6: { name: 'GREEN', ink: 29, width: 0.45, hex: '#00cc99' },
7: { name: 'BLUE', ink: 36, width: 0.45, hex: '#0040ff' },
8: { name: 'BLACK', ink: 49, width: 0.45, hex: '#333333' },
9: { name: 'SEPIA', ink: 117, width: 0.45, hex: '#856647' }
};
const strokeOpacity = 0.8;
/**
* =======================================
* Generative Rules (Mutable)
* =======================================
*/
let generationRules = {
numColumns: 50,
numRows: 50,
segmentLength: 20,
maxRotation: 0.1, // max deviation from baseAngle per line
horizontalJitter: 5,
verticalSpacing: 15,
horizontalSpacing: 15,
baseAngle: 0 // will be set dynamically in setup()
};
/**
* =======================================
* Global Variables
* =======================================
*/
let canvasWidthPixels;
let canvasHeightPixels;
let workAreaMarginInches = 2;
let workAreaMarginPixels;
let selectedColorIndices = [];
let usedColors = new Set();
let strokeWeightValue;
let pathData = {};
function applyStrokeWithOpacity(hexColor) {
let c = color(hexColor);
c.setAlpha(255 * strokeOpacity);
stroke(c);
}
function setup() {
canvasWidthPixels = canvasWidthInches * pixelsPerInch;
canvasHeightPixels = canvasHeightInches * pixelsPerInch;
createCanvas(canvasWidthPixels, canvasHeightPixels);
workAreaMarginInches = random(1.5, 4);
workAreaMarginPixels = workAreaMarginInches * pixelsPerInch;
adjustForStrokeWeight();
// Set a random baseAngle for the entire composition
// For a subtle tilt, choose a limited range. For broader variation, use a larger range.
generationRules.baseAngle = random(-PI/4, PI/4);
selectColors();
initializePaths();
noLoop();
}
function adjustForStrokeWeight() {
strokeWeightValue = 2;
workAreaMarginPixels += strokeWeightValue / 2;
}
function selectColors() {
let numColorsToUse = floor(random(1, 10));
selectedColorIndices = selectRandomColorIndices(numColorsToUse);
}
function selectRandomColorIndices(n) {
let indices = Object.keys(colors).map(k => parseInt(k));
let selected = [];
while (selected.length < n && indices.length > 0) {
let idx = floor(random(indices.length));
selected.push(indices[idx]);
indices.splice(idx, 1);
}
return selected.sort((a, b) => a - b);
}
function initializePaths() {
for (let ci of selectedColorIndices) {
pathData[ci] = [];
}
}
function draw() {
background(255);
drawMarkers();
drawComposition();
addColorPaletteBars();
}
function drawComposition() {
let areaWidth = width - 2 * workAreaMarginPixels;
let areaHeight = height - 2 * workAreaMarginPixels;
let cols = generationRules.numColumns;
let rows = generationRules.numRows;
let hSpacing = generationRules.horizontalSpacing;
let vSpacing = generationRules.verticalSpacing;
let baseAngle = generationRules.baseAngle;
let segLength = generationRules.segmentLength;
// Adjust spacing if needed
let totalWidth = hSpacing * (cols - 1);
let totalHeight = vSpacing * (rows - 1);
if (totalWidth > areaWidth) {
hSpacing = areaWidth / (cols - 1);
}
if (totalHeight > areaHeight) {
vSpacing = areaHeight / (rows - 1);
}
let startX = (width - (hSpacing * (cols - 1))) / 2;
let startY = (height - (vSpacing * (rows - 1))) / 2;
for (let col = 0; col < cols; col++) {
for (let row = 0; row < rows; row++) {
let x = startX + col * hSpacing;
let y = startY + row * vSpacing;
// Slight horizontal jitter
x += random(-generationRules.horizontalJitter, generationRules.horizontalJitter);
// Small random deviation from baseAngle
let angleOffset = random(-generationRules.maxRotation, generationRules.maxRotation);
let finalAngle = baseAngle + angleOffset;
let chosenColorIndex = random(selectedColorIndices);
let cInfo = colors[chosenColorIndex];
strokeWeight(strokeWeightValue);
applyStrokeWithOpacity(cInfo.hex);
usedColors.add(chosenColorIndex);
// Compute line endpoints using finalAngle
let x1 = x - (segLength / 2) * cos(finalAngle);
let y1 = y - (segLength / 2) * sin(finalAngle);
let x2 = x + (segLength / 2) * cos(finalAngle);
let y2 = y + (segLength / 2) * sin(finalAngle);
line(x1, y1, x2, y2);
// Track path data
if (!pathData[chosenColorIndex]) {
pathData[chosenColorIndex] = [];
}
pathData[chosenColorIndex].push({x1, y1, x2, y2});
}
}
}
function drawMarkers() {
strokeWeight(1);
applyStrokeWithOpacity('#000000');
line(0, 0, 5, 0);
line(0, 0, 0, 5);
line(width - 5, height, width, height);
line(width, height - 5, width, height);
}
function addColorPaletteBars() {
let paletteLength = 300;
let barHeight = 2;
let gapSize = 7;
let yStart = height - (1.25 * pixelsPerInch);
let usedColorsOrdered = selectedColorIndices;
let numColors = usedColorsOrdered.length;
if (numColors === 0) return;
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
strokeWeight(barHeight);
for (let i = 0; i < numColors; i++) {
let colorIndex = usedColorsOrdered[i];
let colorValue = colors[colorIndex].hex;
let x = xStart + i * (segmentLength + gapSize);
applyStrokeWithOpacity(colorValue);
line(x, yStart, x + segmentLength, yStart);
}
}
function resetCanvas() {
usedColors.clear();
// Re-randomize the baseAngle to get a new orientation on reset
generationRules.baseAngle = random(-PI/4, PI/4);
for (let ci of selectedColorIndices) {
pathData[ci] = [];
}
background(255);
drawMarkers();
drawComposition();
addColorPaletteBars();
}
function keyPressed() {
if (key === ' ') {
saveSVGs();
} else if (key === 'r' || key === 'R') {
resetCanvas();
}
}
function saveSVGs() {
const timestamp = Date.now();
let combinedSVG = generateSVG(0);
saveSVGFile(combinedSVG, `${dayNumber}-ALL-PORTRAIT-${timestamp}.svg`);
let usedColorsOrdered = selectedColorIndices;
usedColorsOrdered.forEach((colorIndex) => {
let colorSVG = generateSVG(colorIndex);
let colorName = colors[colorIndex].name.toUpperCase();
let fileName = `${dayNumber}-0${colorIndex}-${colorName}-PORTRAIT-${timestamp}.svg`;
saveSVGFile(colorSVG, fileName);
});
}
function generateSVG(activeColor = 0) {
let svgContent = `<svg version="1.1" width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"n`;
svgContent += `xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">n`;
// Add markers
svgContent += addMarkersSVG();
if (activeColor === 0) {
// All colors
selectedColorIndices.forEach((colorIndex, idx) => {
svgContent += addSegmentsSVG(colorIndex, idx + 1, 0);
});
} else {
let idx = selectedColorIndices.indexOf(activeColor) + 1;
svgContent += addSegmentsSVG(activeColor, idx, activeColor);
}
svgContent += '</svg>';
return svgContent;
}
function addSegmentsSVG(colorIndex, layerNum, activeColor) {
let color = colors[colorIndex];
let strokeWidth = strokeWeightValue;
let svg = `<g
inkscape:groupmode="layer"
id="layer${layerNum}"
inkscape:label="${colorIndex}-${color.name.toLowerCase()}"
style="display:inline"
stroke="${color.hex}"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="${color.hex}">n`;
if (pathData[colorIndex] && pathData[colorIndex].length > 0) {
let d = '';
for (let seg of pathData[colorIndex]) {
d += `M${seg.x1.toFixed(2)},${seg.y1.toFixed(2)} L${seg.x2.toFixed(2)},${seg.y2.toFixed(2)} `;
}
svg += `<path d="${d.trim()}" stroke-width="${strokeWidth}" fill="none" />n`;
}
// Add palette bar for this color
let usedColorsOrdered = selectedColorIndices;
let i = usedColorsOrdered.indexOf(colorIndex);
if (i >= 0) {
let paletteLength = 300;
let barHeight = 2;
let gapSize = 7;
let yStart = height - (1.25 * pixelsPerInch);
let numColors = usedColorsOrdered.length;
let totalGaps = (numColors - 1) * gapSize;
let segmentLength = (paletteLength - totalGaps) / numColors;
let xStart = (width - paletteLength) / 2;
let x = xStart + i * (segmentLength + gapSize);
svg += `<path d="M${x.toFixed(2)},${yStart.toFixed(2)} H${(x + segmentLength).toFixed(2)}" stroke="${color.hex}" stroke-opacity="${strokeOpacity}" stroke-width="${barHeight}" id="${colorIndex}-${color.name.toLowerCase()}-palettebar" fill="none"/>n`;
}
svg += '</g>n';
return svg;
}
function addMarkersSVG() {
const strokeWidth = 1;
const offset = strokeWidth / 2;
return `<g
inkscape:groupmode="layer"
id="layer0"
inkscape:label="0-markers"
style="display:inline"
stroke="black"
stroke-opacity="${strokeOpacity}"
inkscape:highlight-color="black">n
<path d="M${5 + offset},${offset} L${offset},${offset} L${offset},${5 + offset} M${(width - 5 - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - offset).toFixed(2)} L${(width - offset).toFixed(2)},${(height - 5 - offset).toFixed(2)}" stroke-width="${strokeWidth}" fill="none" />n
</g>n`;
}
function saveSVGFile(svgData, fileName) {
let blob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}What’s New:
- A
baseAngleproperty is now included ingenerationRules. baseAngleis randomized duringsetup()and recalculated on reset, ensuring a new global orientation each time.- Each line’s final orientation is
baseAngleplus a small random offset (angleOffset), creating a tilted array of lines. - Pressing
r(orR) to regenerate the composition will now also randomizebaseAngle, producing a new angled layout.