Skip to content

Instantly share code, notes, and snippets.

@sybrew
Created August 8, 2025 17:46
Show Gist options
  • Save sybrew/63b6f76c354a340c63ea5867d5e3f9a5 to your computer and use it in GitHub Desktop.
Save sybrew/63b6f76c354a340c63ea5867d5e3f9a5 to your computer and use it in GitHub Desktop.
Creates random art
<?php
/**
* Copyright (c) 2025 Sybre Waaijer, CyberWire B.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Get placeholder image
*
* @since 0.0.1184
*
* @param $params array {
* Optional. Parameters for the image.
*
* @type int $width Width of the image in pixels. Default is 192.
* @type int $height Height of the image in pixels. Default is 192.
* }
* @return \WP_REST_Response
*/
function get_placeholder_image( $params ) {
$width = $params['width'] ?? 192;
$height = $params['height'] ?? 192;
if ( ! \extension_loaded( 'gd' ) )
return new \WP_REST_Response(
[ 'message' => 'GD extension is not enabled.' ],
503,
);
// Create image
$image = \imagecreatetruecolor( $width, $height );
if ( ! $image )
return new \WP_REST_Response(
[ 'message' => 'Failed to create image resource.' ],
500,
);
// Enable alpha blending
\imagealphablending( $image, true );
\imagesavealpha( $image, true );
// Calculate scale factors from base SVG of 512x512
$scale_x = $width / 512;
$scale_y = $height / 512;
// Helper function to scale point arrays
$scale_points = fn( $points ) => \array_map(
fn( $value, $index ) => (int) ( $index & 1
? $value * $scale_y
: $value * $scale_x ),
$points,
\array_keys( $points ),
);
// Helper function to add random variation to points
$randomize_points = fn( $points, $variation = 5 ) => \array_map(
fn( $value ) => $value + mt_rand( -$variation, $variation ),
$points,
\array_keys( $points ),
);
// Fill background
\imagefill(
$image,
0,
0,
\imagecolorallocate(
$image,
mt_rand( 175, 225 ), // 255-80, 255-30
mt_rand( 165, 215 ), // 255-90, 255-40
mt_rand( 170, 220 ), // 255-85, 255-35
),
);
$body_color = \imagecolorallocatealpha(
$image,
mt_rand( 0, 75 ), // 255-255, 255-180
mt_rand( 75, 135 ), // 255-180, 255-120
mt_rand( 35, 105 ), // 255-220, 255-150
mt_rand( 20, 50 ),
);
$accent_color = \imagecolorallocatealpha(
$image,
mt_rand( 0, 55 ), // 255-255, 255-200
mt_rand( 105, 155 ), // 255-150, 255-100
mt_rand( 25, 95 ), // 255-230, 255-160
mt_rand( 30, 70 ),
);
// phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine -- let's not.
// Body and head
\imagefilledpolygon(
$image,
$scale_points( $randomize_points(
[
471.82, 171.1, 459.8, 157.49, 443.37, 121.01, 441.78, 110.92,
435.07, 106.86, 418.28, 89.36, 418.28, 89.36, 427.45, 81.78,
424.82, 72.04, 424.82, 72.04, 423.72, 78.74, 406.44, 80.34,
395.34, 81.37, 391.57, 85.11, 391.57, 85.11, 391.52, 85.15,
391.47, 85.15, 387.03, 81.87, 376.94, 81.96, 365.95, 85.34,
349.77, 88.08, 334.7, 95.77, 322.96, 107.54, 308.09, 122.42,
299.72, 142.56, 299.67, 163.59, 299.62, 185.57, 281.87, 203.55,
259.89, 203.55, 141.65, 203.55, 119.68, 203.55, 101.87, 221.36,
101.87, 241.33, 101.87, 440.22, 141.65, 440.22, 141.65, 340.77, // left leg start
// 152.2, 340.77, 162.32, 336.57, 169.77, 329.12, 177.23, 321.66, // hip start -- commented because it tends to create a penis
181.42, 311.54, 181.42, 301.01, 181.42, 290.48, 203.29, 309.06, // belly start
231.61, 320.9, 261.17, 320.9, 274.73, 320.82, 288.29, 318.41, // right leg start
301.05, 313.81, 301.05, 440.24, 340.83, 440.24, 340.83, 291.11,
366.06, 268.56, 380.51, 236.34, 380.61, 202.49, 380.61, 202.47,
380.61, 162.71, 381.06, 162.71, 381.48, 162.67, 381.9, 162.61, // chin start
383.93, 167.7, 390.23, 175.96, 409.74, 179.84, 439.07, 185.67,
437.35, 194.39, 438.9, 200.51, 441.29, 209.96, 459.19, 209.66,
// 463.02, 202.63, 464.61, 199.71, 470.71, 202.53, 473.19, 199.18, // mouth start
// 474.41, 197.46, 473.8, 193.96, 476.37, 191.95, 483.46, 186.48, // -- commented for it creates too much noise
481.28, 181.81, 471.82, 171.1,
],
mt_rand( 0, 15 ),
) ),
$body_color,
);
// Foot
\imagefilledpolygon(
$image,
$scale_points( $randomize_points(
[
368.39, 313.98, 368.39, 313.98, 400.68, 313.98, 400.68, 322.44,
397.31, 339.02, 391.32, 345.01, 385.33, 350.99, 377.21, 354.36,
368.75, 354.36, 368.75, 386.28, 385.67, 386.28, 401.91, 380.04,
413.88, 368.07, 425.85, 356.1, 432.59, 339.86, 432.59, 322.94,
432.59, 291.02, 400.68, 291.02, 400.31, 291.02, 382.68, 282.06,
368.39, 296.35, 368.39, 313.98,
],
mt_rand( 0, 5 ),
) ),
$accent_color,
);
// Tail
\imagefilledpolygon(
$image,
$scale_points( $randomize_points(
[
33.93, 242.28, 33.93, 361.62, 73.71, 361.62, 73.71, 202.5,
73.71, 202.5, 51.74, 202.5, 33.93, 220.31, 33.93, 242.28,
],
mt_rand( 0, 15 ),
) ),
$accent_color,
);
// phpcs:enable WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
// Make random rectangle coordinates, opposing the sides
$flip_rect_bias_x = mt_rand( 0, 1 );
$flip_rect_bias_y = mt_rand( 0, 1 );
for ( $i = mt_rand( 1, 2 ); $i--; ) {
$biased_x = $flip_rect_bias_x
? mt_rand( 275, 450 ) // bias right
: mt_rand( 50, 175 ); // bias left
$biased_y = $flip_rect_bias_y
? mt_rand( 275, 450 ) // bias bottom
: mt_rand( 50, 175 ); // bias top
// Toggle bias for next rectangle
$flip_rect_bias_x = ! $flip_rect_bias_x;
$flip_rect_bias_y = ! $flip_rect_bias_y;
// Generate dimensions with rectangular bias
$_width = mt_rand( 60, 180 );
$_height = mt_rand( 20, 80 );
// Randomly swap width/height to create both horizontal and vertical rectangles
if ( mt_rand( 0, 1 ) ) {
$__width = $_width; // Store original width
$_width = $_height;
$_height = $__width;
}
$random_rect = [
$biased_x,
$biased_y,
$_width,
$_height,
];
$scaled_rect = \array_map(
fn( $value, $index ) => (int) ( $value * (
$index & 1 ? $scale_y : $scale_x
) ),
$random_rect,
\array_keys( $random_rect )
);
$rect_color = \imagecolorallocatealpha(
$image,
mt_rand( 100, 255 ),
mt_rand( 100, 255 ),
mt_rand( 100, 255 ),
mt_rand( 40, 80 ),
);
\imagefilledrectangle(
$image,
$scaled_rect[0],
$scaled_rect[1],
$scaled_rect[0] + $scaled_rect[2],
$scaled_rect[1] + $scaled_rect[3],
$rect_color,
);
}
// Add random burning rectangle
$burn_color = \imagecolorallocatealpha(
$image,
mt_rand( 0, 100 ),
mt_rand( 0, 100 ),
mt_rand( 0, 100 ),
mt_rand( 70, 120 ),
);
$burn_rect = \array_map(
fn( $val ) => (int) ( $val * ( $scale_x + $scale_y ) / 2 ),
[
mt_rand( 50, 300 ),
mt_rand( 50, 300 ),
mt_rand( 60, 120 ),
mt_rand( 60, 120 ),
]
);
\imagefilledrectangle(
$image,
$burn_rect[0],
$burn_rect[1],
$burn_rect[0] + $burn_rect[2],
$burn_rect[1] + $burn_rect[3],
$burn_color,
);
// Add 2-4 random color circles biased toward top half
for ( $i = mt_rand( 2, 4 ); $i--; ) {
$circle_color = \imagecolorallocatealpha(
$image,
mt_rand( 150, 255 ),
mt_rand( 150, 255 ),
mt_rand( 50, 150 ),
mt_rand( 50, 90 ),
);
// Bias Y position toward top half with weighted random
$y_bias = mt_rand( 1, 100 );
$y_pos = $y_bias <= 70
? mt_rand( 50, 200 ) // 70% chance in upper area
: mt_rand( 150, 300 ); // 30% chance in middle area
$circle_data = \array_map(
fn( $val ) => (int) ( $val * ( $scale_x + $scale_y ) / 2 ),
[
mt_rand( 80, 350 ), // x position
$y_pos, // biased y position
mt_rand( 25, 60 ), // radius
]
);
\imagefilledellipse(
$image,
$circle_data[0],
$circle_data[1],
$circle_data[2] * 2,
$circle_data[2] * 2,
$circle_color,
);
}
// Add texture effects
\imagesetthickness(
$image,
(int) max( 1, 3 * ( $scale_x + $scale_y ) / 2 ),
);
// Add 2-4 random arcs
for ( $i = mt_rand( 2, 4 ); $i--; ) {
$start_angle = mt_rand( 0, 360 );
$arc_length = mt_rand( 30, 180 ); // Control arc length to avoid full circles
$end_angle = $start_angle + $arc_length;
$curve = [
mt_rand( 50, 450 ), // x center
mt_rand( 50, 450 ), // y center
mt_rand( 120, 300 ), // width (increased for more elliptical)
mt_rand( 20, 60 ), // height (decreased for more linear)
$start_angle, // start angle
$end_angle, // end angle
];
$randomized_curve = $randomize_points( $curve, 15 );
$scaled_curve = $scale_points( $randomized_curve );
\imagearc(
$image,
$scaled_curve[0],
$scaled_curve[1],
$scaled_curve[2],
$scaled_curve[3],
$scaled_curve[4],
$scaled_curve[5],
\imagecolorallocatealpha(
$image,
mt_rand( 180, 255 ), // higher reds for gold
mt_rand( 140, 200 ), // moderate greens for gold
mt_rand( 50, 120 ), // lower blues for gold
mt_rand( 60, 100 ),
),
);
}
// Add random sun
\imagefilledrectangle(
$image,
(int) ( $width * 0.85 ),
0,
$width - 1,
(int) ( $height * 0.15 ),
\imagecolorallocatealpha(
$image,
mt_rand( 200, 255 ), // bias toward yellows/oranges for sun
mt_rand( 150, 220 ), // moderate yellows for sun warmth
mt_rand( 50, 120 ), // lower blues for sun colors
mt_rand( 20, 60 ),
),
);
// Add random noise
for ( $i = 0; $i < 1000; $i++ )
\imagesetpixel(
$image,
mt_rand( 0, $width - 1 ),
mt_rand( 0, $height - 1 ),
\imagecolorallocatealpha(
$image,
mt_rand( 0, 255 ),
mt_rand( 0, 255 ),
mt_rand( 0, 255 ),
mt_rand( 50, 100 ),
)
);
// Add 2-4 random boxed lines
for ( $i = mt_rand( 2, 4 ); $i--; ) {
// Randomly choose border width
$border_width = mt_rand( 10, 25 );
// Randomly choose border color
$border_color = \imagecolorallocatealpha(
$image,
mt_rand( 0, 255 ),
mt_rand( 0, 255 ),
mt_rand( 0, 255 ),
mt_rand( 50, 100 ),
);
// Draw rectangle with max 1/3rd of the image size
$rect_width = mt_rand( 10, $width / 3 );
$rect_height = mt_rand( 10, $height / 3 );
$rect_x = mt_rand( 0, $width - $rect_width );
$rect_y = mt_rand( 0, $height - $rect_height );
\imagerectangle(
$image,
$rect_x,
$rect_y,
$rect_x + $rect_width,
$rect_y + $rect_height,
$border_color,
);
}
// Add 2 random canvas borders around the image, consider scaling
for ( $i = mt_rand( 1, 4 ); $i--; ) {
// Randomly choose border width (scaled)
$border_width = mt_rand( 5, 15 ) * ( $scale_x + $scale_y ) / 2;
// Randomly choose border color
$border_color = \imagecolorallocatealpha(
$image,
...(
60 >= mt_rand( 1, 100 )
? [ // 60% bronze
mt_rand( 139, 205 ),
mt_rand( 69, 115 ),
mt_rand( 19, 69 ),
]
: (
70 >= mt_rand( 1, 100 )
? [ // 28% wood
mt_rand( 101, 160 ),
mt_rand( 67, 101 ),
mt_rand( 33, 67 ),
]
: [ // 12% white
mt_rand( 220, 255 ),
mt_rand( 220, 255 ),
mt_rand( 220, 255 ),
]
)
),
// Workaround the positional argument unpacking issue
...[ mt_rand( 50, 100 ) ],
);
// Draw border rectangle that touches image edges
for ( $thickness = 0; $thickness < $border_width; $thickness++ ) {
\imagerectangle(
$image,
$thickness,
$thickness,
$width - 1 - $thickness,
$height - 1 - $thickness,
$border_color,
);
}
}
// Start output buffering to capture the image data
\ob_start();
\imagepng( $image, null, 6 );
$image_data = \ob_get_clean();
// Destroy the image resource
\imagedestroy( $image );
return $image_data;
}
$px = 192;
header( 'Content-Type: text/html; charset=UTF-8' );
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Placeholder Grid</title>
<style>
body {
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
background: cadetblue;
}
image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(<?= $px ?>px, 1fr));
gap: <?= $px / 10 ?>px;
width: 100%;
padding: <?= $px / 10 ?>px;
}
img {
width: <?= $px ?>px;
height: <?= $px ?>px;
object-fit: cover;
margin: 0;
padding: 0;
display: block;
}
</style>
</head>
<body>
<image-grid>
<?php
for ( $i = 100; $i--; ) {
$png = get_placeholder_image( [
'width' => $px,
'height' => $px,
] );
$b64 = base64_encode($png);
echo "<img src=data:image/png;base64,$b64>";
}
?>
</image-grid>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment