diff options
author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-16 01:30:42 -0500 |
---|---|---|
committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-16 01:30:42 -0500 |
commit | 6a4de8d05ce46dc29ac69f696c419a57e604f516 (patch) | |
tree | c0368c0df9dec2a6f2835707681c215aae448e71 | |
parent | 953627770c09cbb6918a0816f4e5974bb57044e1 (diff) |
sorting?
-rw-r--r-- | src/client/apis/gpt/customization.ts | 2 | ||||
-rw-r--r-- | src/client/views/ExtractColors.ts | 111 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 3 |
4 files changed, 117 insertions, 4 deletions
diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts index fec0a50f9..f9dabd863 100644 --- a/src/client/apis/gpt/customization.ts +++ b/src/client/apis/gpt/customization.ts @@ -82,7 +82,7 @@ export const generatePalette = async (inputData: any) => { textSize: 'number', textContent: 'string', }) + - '. Finally, imageColors are the main hex colors of the images in the collection. You are going to generate three distinct variants of color palettes for the user to choose from based mostly on collectionDescription, and loosely on the text content and text size of the documents. You should also take the imageColors into account, but mostly rely on the text content. The variants should start with a light palette and grow increasingly more intense and vibrant. Return a json array of three objects in this format:' + + '. Finally, imageColors are the main hex colors of the images in the collection. You are going to generate three distinct variants of color palettes for the user to choose from based mostly on collectionDescription, and loosely on the text content and text size of the documents. You should slightly take imageColors into account, but primarly focus on crafting a palette that matches the text content. The variants should start with a light palette and grow increasingly more intense and vibrant. Return a json array of three objects in this format:' + JSON.stringify({ collectionBackgroundColor: 'string', documentsWithColors: 'DocumentWithColor[]', diff --git a/src/client/views/ExtractColors.ts b/src/client/views/ExtractColors.ts index f78d9a355..f6928c52a 100644 --- a/src/client/views/ExtractColors.ts +++ b/src/client/views/ExtractColors.ts @@ -1,4 +1,5 @@ import { extractColors } from 'extract-colors'; +import { FinalColor } from 'extract-colors/lib/types/Color'; // Manages image color extraction export class ExtractColors { @@ -54,4 +55,114 @@ export class ExtractColors { const colors = await extractColors(img, { distance: 0.35 }); return colors; }; + + static simpleSort = (colors: FinalColor[]): FinalColor[] => { + colors.sort((a, b) => { + if (a.hue !== b.hue) { + return b.hue - a.hue; + } else { + return b.saturation - a.saturation; + } + }); + return colors; + }; + + static sortColors(colors: FinalColor[]): FinalColor[] { + // Convert color from RGB to CIELAB format + const convertToLab = (color: FinalColor): number[] => { + const r = color.red / 255; + const g = color.green / 255; + const b = color.blue / 255; + + const x = r * 0.4124564 + g * 0.3575761 + b * 0.1804375; + const y = r * 0.2126729 + g * 0.7151522 + b * 0.072175; + const z = r * 0.0193339 + g * 0.119192 + b * 0.9503041; + + const pivot = 0.008856; + const factor = 903.3; + + const fx = x > pivot ? Math.cbrt(x) : (factor * x + 16) / 116; + const fy = y > pivot ? Math.cbrt(y) : (factor * y + 16) / 116; + const fz = z > pivot ? Math.cbrt(z) : (factor * z + 16) / 116; + + const L = 116 * fy - 16; + const a = (fx - fy) * 500; + const b1 = (fy - fz) * 200; + + return [L, a, b1]; + }; + + // Sort colors using CIELAB distance for smooth transitions + colors.sort((colorA, colorB) => { + const labA = convertToLab(colorA); + const labB = convertToLab(colorB); + + // Calculate Euclidean distance in CIELAB space + const distanceA = Math.sqrt(Math.pow(labA[0] - labB[0], 2) + Math.pow(labA[1] - labB[1], 2) + Math.pow(labA[2] - labB[2], 2)); + + const distanceB = Math.sqrt(Math.pow(labB[0] - labA[0], 2) + Math.pow(labB[1] - labA[1], 2) + Math.pow(labB[2] - labA[2], 2)); + + return distanceA - distanceB; // Sort by CIELAB distance + }); + + return colors; + } + + static hexToFinalColor = (hex: string): FinalColor => { + const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + + if (!rgb) { + throw new Error('Invalid hex color format.'); + } + + const red = parseInt(rgb[1], 16); + const green = parseInt(rgb[2], 16); + const blue = parseInt(rgb[3], 16); + + const max = Math.max(red, green, blue); + const min = Math.min(red, green, blue); + const area = max - min; + const intensity = (max + min) / 2; + + let hue = 0; + let saturation = 0; + let lightness = intensity; + + if (area !== 0) { + saturation = area / (1 - Math.abs(2 * intensity - 1)); + if (max === red) { + hue = (60 * ((green - blue) / area) + 360) % 360; + } else if (max === green) { + hue = (60 * ((blue - red) / area) + 120) % 360; + } else { + hue = (60 * ((red - green) / area) + 240) % 360; + } + } + + return { + hex, + red, + green, + blue, + area, + hue, + saturation, + lightness, + intensity, + }; + }; } + +// for reference + +// type FinalColor = { +// hex: string; +// red: number; +// green: number; +// blue: number; +// area: number; +// hue: number; +// saturation: number; +// lightness: number; +// intensity: number; +// } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index fb2d811f4..86784d467 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -41,6 +41,7 @@ import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; import { FaFillDrip } from 'react-icons/fa'; import { GeneratedResponse } from '../apis/gpt/customization'; +import { ExtractColors } from './ExtractColors'; const _global = (window /* browser */ || global) /* node */ as any; interface PropertiesViewProps { @@ -1191,8 +1192,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { ? this.generatedStyles.map((style, i) => ( <div className="propertiesView-palette" style={{ display: 'flex', gap: '4px', backgroundColor: this.selectedStyle === i ? StrCast(Doc.UserDoc().userVariantColor) : '#00000000' }} onClick={() => this.styleCollection(i)}> <div style={{ width: '24px', height: '24px', backgroundColor: style.collectionBackgroundColor, borderRadius: '2px' }}></div> - {style.documentsWithColors.map(c => ( - <div style={{ width: '24px', height: '24px', backgroundColor: c.color, borderRadius: '2px' }}></div> + {ExtractColors.sortColors(style.documentsWithColors.map(doc => ExtractColors.hexToFinalColor(doc.color))).map(c => ( + <div style={{ width: '24px', height: '24px', backgroundColor: c.hex, borderRadius: '2px' }}></div> ))} </div> )) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fb2de5647..b0be060bd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1820,10 +1820,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action gptStyling = async () => { this.openProperties(); + PropertiesView.Instance?.setGeneratedStyles([]); console.log('Title', this.rootDoc.title); console.log('bgcolor', this.layoutDoc._backgroundColor); // doc.backgroundColor - console.log('styling'); const inputDocs = this.childDocs.filter(doc => doc.type == 'rich text'); const imgDocs = this.childDocs.filter(doc => doc.type == 'image'); const imgUrls = imgDocs.map(doc => this.choosePath((doc.data as ImageField).url)); @@ -1837,6 +1837,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection console.log('Hexes', colorHexes); PropertiesView.Instance?.setInputDocs(inputDocs); + // also pass it colors const gptInput = inputDocs.map((doc, i) => ({ id: i, |