diff --git a/frontend/src/app.js b/frontend/src/app.js
index 2b1c2fb..7d57654 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -43,6 +43,10 @@ app.get( '/showcase.js', ( req, res ) => {
res.sendFile( path.join( __dirname + '/client/showcase.js' ) );
} );
+app.get( '/colorPaletteExtractor.js', ( req, res ) => {
+ res.sendFile( path.join( __dirname + '/client/colorPaletteExtractor.js' ) );
+} );
+
app.get( '/showcase.css', ( req, res ) => {
res.sendFile( path.join( __dirname + '/client/showcase.css' ) );
} );
diff --git a/frontend/src/client/colorPaletteExtractor.js b/frontend/src/client/colorPaletteExtractor.js
new file mode 100644
index 0000000..cd9d2f7
--- /dev/null
+++ b/frontend/src/client/colorPaletteExtractor.js
@@ -0,0 +1,145 @@
+// https://github.com/zygisS22/color-palette-extraction/blob/master/index.js
+
+
+const buildRgb = ( imageData ) => {
+ const rgbValues = [];
+ // note that we are loopin every 4!
+ // for every Red, Green, Blue and Alpha
+ for (let i = 0; i < imageData.length; i += 4) {
+ const rgb = {
+ r: imageData[ i ],
+ g: imageData[ i + 1 ],
+ b: imageData[ i + 2 ],
+ };
+
+ rgbValues.push(rgb);
+ }
+
+ return rgbValues;
+};
+
+// returns what color channel has the biggest difference
+const findBiggestColorRange = (rgbValues) => {
+ /**
+ * Min is initialized to the maximum value posible
+ * from there we procced to find the minimum value for that color channel
+ *
+ * Max is initialized to the minimum value posible
+ * from there we procced to fin the maximum value for that color channel
+ */
+ let rMin = Number.MAX_VALUE;
+ let gMin = Number.MAX_VALUE;
+ let bMin = Number.MAX_VALUE;
+
+ let rMax = Number.MIN_VALUE;
+ let gMax = Number.MIN_VALUE;
+ let bMax = Number.MIN_VALUE;
+
+ rgbValues.forEach((pixel) => {
+ rMin = Math.min(rMin, pixel.r);
+ gMin = Math.min(gMin, pixel.g);
+ bMin = Math.min(bMin, pixel.b);
+
+ rMax = Math.max(rMax, pixel.r);
+ gMax = Math.max(gMax, pixel.g);
+ bMax = Math.max(bMax, pixel.b);
+ });
+
+ const rRange = rMax - rMin;
+ const gRange = gMax - gMin;
+ const bRange = bMax - bMin;
+
+ // determine which color has the biggest difference
+ const biggestRange = Math.max(rRange, gRange, bRange);
+ if (biggestRange === rRange) {
+ return 'r';
+ } else if (biggestRange === gRange) {
+ return 'g';
+ } else {
+ return 'b';
+ }
+};
+
+/**
+ * Median cut implementation
+ * can be found here -> https://en.wikipedia.org/wiki/Median_cut
+ */
+const quantization = ( rgbValues, depth ) => {
+ const MAX_DEPTH = 4;
+
+ // Base case
+ if ( depth === MAX_DEPTH || rgbValues.length === 0 ) {
+ const color = rgbValues.reduce(
+ ( prev, curr ) => {
+ prev.r += curr.r;
+ prev.g += curr.g;
+ prev.b += curr.b;
+
+ return prev;
+ },
+ {
+ r: 0,
+ g: 0,
+ b: 0,
+ }
+ );
+
+ color.r = Math.round( color.r / rgbValues.length );
+ color.g = Math.round( color.g / rgbValues.length );
+ color.b = Math.round( color.b / rgbValues.length );
+
+ return [color];
+ }
+
+ /**
+ * Recursively do the following:
+ * 1. Find the pixel channel (red,green or blue) with biggest difference/range
+ * 2. Order by this channel
+ * 3. Divide in half the rgb colors list
+ * 4. Repeat process again, until desired depth or base case
+ */
+ const componentToSortBy = findBiggestColorRange( rgbValues );
+ rgbValues.sort( ( p1, p2 ) => {
+ return p1[ componentToSortBy ] - p2[ componentToSortBy ];
+ } );
+
+ const mid = rgbValues.length / 2;
+ return [
+ ...quantization( rgbValues.slice( 0, mid ), depth + 1 ),
+ ...quantization( rgbValues.slice( mid + 1 ), depth + 1 ),
+ ];
+};
+
+const getColourPalette = ( imageURL ) => {
+ return new Promise( ( resolve, reject ) => {
+
+ // Set the canvas size to be the same as of the uploaded image
+ let image = new Image();
+ image.src = imageURL;
+ const canvas = document.getElementById( 'canvas' );
+ setTimeout( () => {
+ canvas.width = image.width ?? 500;
+ canvas.height = image.height ?? 500;
+ const ctx = canvas.getContext( '2d' );
+ ctx.drawImage(image, 0, 0);
+
+ /**
+ * getImageData returns an array full of RGBA values
+ * each pixel consists of four values: the red value of the colour, the green, the blue and the alpha
+ * (transparency). For array value consistency reasons,
+ * the alpha is not from 0 to 1 like it is in the RGBA of CSS, but from 0 to 255.
+ */
+ const imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height );
+
+ // Convert the image data to RGB values so its much simpler
+ const rgbArray = buildRgb( imageData.data );
+
+ /**
+ * Color quantization
+ * A process that reduces the number of colors used in an image
+ * while trying to visually maintin the original image as much as possible
+ */
+ resolve( quantization( rgbArray, 0 ) );
+ }, 1000 );
+ } );
+};
\ No newline at end of file
diff --git a/frontend/src/client/showcase.css b/frontend/src/client/showcase.css
index d6d9772..b3fe7a5 100644
--- a/frontend/src/client/showcase.css
+++ b/frontend/src/client/showcase.css
@@ -170,4 +170,22 @@ body {
object-position: center;
font-size: 5vw !important;
user-select: none;
+}
+
+.current-song {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ height: 55vh;
+ width: 100%;
+}
+
+.fancy-view-song-art {
+ height: 30vh;
+ width: 30vh;
+ object-fit: cover;
+ object-position: center;
+ margin-bottom: 20px;
+ font-size: 30vh !important;
}
\ No newline at end of file
diff --git a/frontend/src/client/showcase.html b/frontend/src/client/showcase.html
index 97b09b9..be80008 100644
--- a/frontend/src/client/showcase.html
+++ b/frontend/src/client/showcase.html
@@ -11,32 +11,41 @@
-
Ok
-
-
-
music_note
-
![]()
-
-
-
-
-
+
+
+
music_note
+
![]()
+
{{ playingSong.title }}
+
{{ playingSong.artist }}
+
+
+
+
music_note
+
![]()
+
+
pause
+
+
{{ song.title }}
+
{{ song.artist }}
-
pause
-
-
{{ song.title }}
-
{{ song.artist }}
-
+
-
Loading...
+
+