image wheel finish
This commit is contained in:
parent
4594982834
commit
63f3c55041
|
@ -11,10 +11,23 @@
|
||||||
let tripOptions = [];
|
let tripOptions = [];
|
||||||
let memory = null;
|
let memory = null;
|
||||||
let tripId = '';
|
let tripId = '';
|
||||||
|
let currentImageIndex = 0;
|
||||||
|
let gradientLayers = [];
|
||||||
|
let columnGroups = [];
|
||||||
|
let rotationAngle = 0;
|
||||||
|
|
||||||
|
$: tripId = page.params.tripId;
|
||||||
|
$: memoryId = page.params.memoryId;
|
||||||
|
$: currentImage = memory?.images?.[currentImageIndex];
|
||||||
$: {
|
$: {
|
||||||
tripId = page.params.tripId;
|
if (memory?.images?.length > 0) {
|
||||||
memoryId = page.params.memoryId;
|
const imageCount = memory.images.length;
|
||||||
|
const sliceAngle = 360 / imageCount;
|
||||||
|
rotationAngle = 90 - sliceAngle / 2 + sliceAngle * currentImageIndex;
|
||||||
|
console.log('Rotation angle:', rotationAngle);
|
||||||
|
} else {
|
||||||
|
rotationAngle = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -55,6 +68,10 @@
|
||||||
tripOptions.unshift(current);
|
tripOptions.unshift(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (memory?.images?.length) {
|
||||||
|
columnGroups = await extractColumnwiseColors(memory.images);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Trip or memory not found');
|
console.error('Trip or memory not found');
|
||||||
}
|
}
|
||||||
|
@ -64,8 +81,128 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let gradientColors = ['#e74c3c', '#f1c40f', '#2ecc71', '#3498db', '#9b59b6'];
|
$: if (memory?.images?.length && columnGroups.length) {
|
||||||
$: gradientStyle = `conic-gradient(${gradientColors.map((c, i) => `${c} ${i * 72}deg ${(i + 1) * 72}deg`).join(',')})`;
|
const angleOffset = -(360 / memory.images.length) * currentImageIndex;
|
||||||
|
const gradients = makeConcentricGradients(columnGroups, angleOffset);
|
||||||
|
|
||||||
|
//to make donut form
|
||||||
|
const MASK_COUNT = 3;
|
||||||
|
for (let i = 0; i < MASK_COUNT; i++) {
|
||||||
|
gradients.push('radial-gradient(circle, var(--black) 100%)');
|
||||||
|
}
|
||||||
|
|
||||||
|
gradientLayers = gradients.map((bg, i) => {
|
||||||
|
const scale = 1 - i * 0.1;
|
||||||
|
return `background: ${bg};
|
||||||
|
width: ${scale * 100}%;
|
||||||
|
height: ${scale * 100}%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert rgb to hsb
|
||||||
|
function rgbToHsb(r, g, b) {
|
||||||
|
const r_ = r / 255, g_ = g / 255, b_ = b / 255;
|
||||||
|
const max = Math.max(r_, g_, b_), min = Math.min(r_, g_, b_);
|
||||||
|
const delta = max - min;
|
||||||
|
|
||||||
|
let h = 0;
|
||||||
|
if (delta !== 0) {
|
||||||
|
if (max === r_) h = ((g_ - b_) / delta) % 6;
|
||||||
|
else if (max === g_) h = (b_ - r_) / delta + 2;
|
||||||
|
else h = (r_ - g_) / delta + 4;
|
||||||
|
h = Math.round(h * 60);
|
||||||
|
if (h < 0) h += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = max === 0 ? 0 : delta / max;
|
||||||
|
const v = max;
|
||||||
|
|
||||||
|
return [h, s * 100, v * 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getImageData(url) {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'Anonymous';
|
||||||
|
img.src = url;
|
||||||
|
await img.decode();
|
||||||
|
|
||||||
|
//to shorten rendering time
|
||||||
|
const MAX_WIDTH = 100;
|
||||||
|
const ratio = MAX_WIDTH / img.width;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = MAX_WIDTH;
|
||||||
|
canvas.height = Math.round(img.height * ratio);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
//make column colorset from image
|
||||||
|
function getColumnColors(imageData, columnCount = 5) {
|
||||||
|
const { data, width, height } = imageData;
|
||||||
|
const columnWidth = Math.floor(width / columnCount);
|
||||||
|
const columns = Array.from({ length: columnCount }, () => []);
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
const colIndex = Math.min(Math.floor(x / columnWidth), columnCount - 1);
|
||||||
|
const idx = (y * width + x) * 4;
|
||||||
|
const r = data[idx], g = data[idx + 1], b = data[idx + 2];
|
||||||
|
|
||||||
|
const [h, s, br] = rgbToHsb(r, g, b);
|
||||||
|
if (s < 10 || br < 20) continue; //color correction
|
||||||
|
|
||||||
|
const rgb = `rgb(${r},${g},${b})`;
|
||||||
|
columns[colIndex].push(rgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns.map(column => {
|
||||||
|
const count = {};
|
||||||
|
column.forEach(color => count[color] = (count[color] || 0) + 1);
|
||||||
|
return Object.entries(count).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'rgb(0,0,0)';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//make column color set for gradient
|
||||||
|
async function extractColumnwiseColors(imageUrls) {
|
||||||
|
const columnColorGroups = [[], [], [], [], []];
|
||||||
|
|
||||||
|
for (const url of imageUrls) {
|
||||||
|
const imageData = await getImageData(url);
|
||||||
|
const colColors = getColumnColors(imageData);
|
||||||
|
colColors.forEach((color, index) => {
|
||||||
|
columnColorGroups[index].push(color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnColorGroups.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeConcentricGradients(groups, rotationOffset = 0) {
|
||||||
|
return groups.map(colors => {
|
||||||
|
const step = 360 / colors.length;
|
||||||
|
return `conic-gradient(from ${rotationOffset}deg, ${colors.map((c, i) => `${c} ${i * step}deg ${(i + 1) * step}deg`).join(', ')})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextImage() {
|
||||||
|
if (memory?.images?.length > 0) {
|
||||||
|
currentImageIndex = (currentImageIndex + 1) % memory.images.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevImage() {
|
||||||
|
if (memory?.images?.length > 0) {
|
||||||
|
currentImageIndex = (currentImageIndex - 1 + memory.images.length) % memory.images.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
@ -88,22 +225,27 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wheel-container">
|
<div class="wheel-container">
|
||||||
<div class="gradient-wheel" style="background-image: {gradientStyle};"></div>
|
<div class="wheel-mask">
|
||||||
</div>
|
<div
|
||||||
|
class="gradient-wheel"
|
||||||
<div class="image-preview">
|
style={`transform: translateY(-50%) rotate(${rotationAngle}deg); transform-origin: center center;`}
|
||||||
{#if memory.images && memory.images.length > 0}
|
>
|
||||||
<h2>Images</h2>
|
{#each gradientLayers as style}
|
||||||
<div class="image-grid">
|
<div class="layer" style={style}></div>
|
||||||
{#each memory.images as img}
|
|
||||||
<img src={typeof img === 'string' ? img : URL.createObjectURL(img)} alt="memory image" />
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
</div>
|
||||||
<p class="no-image">No images uploaded.</p>
|
|
||||||
|
{#if currentImage}
|
||||||
|
<img class="preview-img" src={currentImage} alt="Current Image" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="arrow-controls">
|
||||||
|
<button on:click={prevImage}>▲</button>
|
||||||
|
<button on:click={nextImage}>▼</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if} <!-- ✅ 이 줄 빠지면 Svelte 에러 발생 -->
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@ -184,44 +326,67 @@
|
||||||
background-color: var(--gray-100);
|
background-color: var(--gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gradient-wheel {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
margin: 2rem auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wheel-container {
|
.wheel-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
height: 40vw;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview {
|
.wheel-mask {
|
||||||
padding: 1rem 2rem;
|
width: 50vw;
|
||||||
color: white;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview h2 {
|
.gradient-wheel {
|
||||||
font-size: 1.25rem;
|
position: absolute;
|
||||||
margin-bottom: 0.75rem;
|
width: 35vw;
|
||||||
|
height: 35vw;
|
||||||
|
left: -17.5vw;
|
||||||
|
top: 50%;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-grid {
|
.layer {
|
||||||
display: grid;
|
position: absolute;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
border-radius: 50%;
|
||||||
gap: 1rem;
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-grid img {
|
.preview-img {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
border-radius: 8px;
|
top: 50%;
|
||||||
|
left: calc(17.5vw + 180px);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
aspect-ratio: 1 / 1;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-image {
|
.arrow-controls {
|
||||||
font-size: 0.95rem;
|
display: flex;
|
||||||
color: var(--gray-400);
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(17.5vw + 340px);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-controls button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user