Skip to content
Snippets Groups Projects
Commit ed7e3ec8 authored by donggni0712's avatar donggni0712
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #8789 failed
Showing
with 800 additions and 0 deletions
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.env
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"recommendations": ["svelte.svelte-vscode"]
}
# 나만의 프로필 만들기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/my-chito-icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:image" content="/my-chito-icon.png" />
<title>나만의 치토_카카아톡</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This diff is collapsed.
{
"name": "my-chito",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"deploy": "aws s3 sync ./dist s3://my-chito --profile=my-chito"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.3",
"@tsconfig/svelte": "^3.0.0",
"gh-pages": "^5.0.0",
"svelte": "^3.55.1",
"svelte-check": "^2.10.3",
"tslib": "^2.5.0",
"typescript": "^4.9.3",
"vite": "^4.2.0"
},
"dependencies": {
"@types/fabric": "^5.3.3",
"fabric": "^5.3.0",
"svelte-awesome-color-picker": "^2.4.1"
}
}
public/my-chito-icon.png

88.5 KiB

<script lang="ts">
import AIRPODS_MAX_IMG from './assets/acc/airpodsmax.png'
import DARK_CIRCLE_IMG from './assets/acc/darkcircles.png'
import MINOI_SUNGLASSES_IMG from './assets/acc/minoisunglasses.png'
import GLASSES_IMG from './assets/acc/glasses.png'
import COOKIE_IMG from './assets/acc/cookies.png'
import COTTON_CANDY_IMG from './assets/acc/cottoncandy.png'
import TOT_IMG from './assets/acc/tot.png'
import FLUSHING from './assets/acc/flushing.png'
import BECHELOR_CAP from './assets/acc/bachelorcap.png'
import pen_icon from './assets/pen.png'
import eraser_icon from './assets/eraser.png'
import select_icon from './assets/select.png'
import BODY from './assets/myChito/body.png'
import BODY_ACC from './assets/myChito/body-acc.png'
import ARM from './assets/myChito/arm.png'
import ARM_ACC from './assets/myChito/arm-acc.png'
import SKIN from './assets/myChito/skin.png'
import PANTS from './assets/myChito/pants.png'
import EYE_BROW from './assets/myChito/eyebrow.png'
import SMALL_HAIR from './assets/myChito/small-hair.png'
import BIG_HAIR from './assets/myChito/big-hair.png'
import LINE from './assets/myChito/chito-line.png'
import ColorPicker from 'svelte-awesome-color-picker';
import {fabric} from "fabric";
import { onMount } from 'svelte';
let canvas;
let inputImage: HTMLInputElement;
let backgroundImage: HTMLInputElement;
let hex = '#8a8a8a';
let body : ChitoImage;
let arm : ChitoImage;
let bodyAcc : ChitoImage;
let armAcc : ChitoImage;
let pants : ChitoImage;
let eyebrow : ChitoImage;
let smallHair : ChitoImage;
let bigHair : ChitoImage;
let skin : ChitoImage;
// 과잠 컬러
let bodyColor = '#233479';
let armColor = '#ffffff';
let bodyAccColor = '#ffffff';
let armAccColor = '#233479';
// 바지 컬러
let pantsColor = '#ffffff';
// 치토 컬러
let skinColor = '#fbe9bd';
let eyebrowColor = '#ffffff';
let bighairColor = '#4364ad';
let smallhairColor = '#6f9fd2';
let isDrawing = false;
let penColor = "#000000"; // 기본 펜 색상
let penSize = 5; // 기본 펜 크기
let width = 600;
let airpods_max : AccImage;
let dark_circle : AccImage;
let minoi_sunglasses : AccImage;
let glasses : AccImage;
let cookies : AccImage;
let cotton_candy : AccImage;
let uniform : AccImage;
let bechelor_cap : AccImage;
let flushing : AccImage;
let currentToolbar = "default";
const setToolbar = (toolbarName) => {
currentToolbar = toolbarName;
isDrawing = false;
canvas.isDrawingMode = isDrawing;
}
class AccImage {
url: string;
isVisible: boolean;
imageInstance: fabric.Image | null = null; // fabric 이미지 객체를 참조하기 위한 변수
constructor(url: string, isVisible = false) {
this.url = url;
this.isVisible = isVisible;
}
addToCanvas(canvas: fabric.Canvas) {
fabric.Image.fromURL(this.url, (img) => {
img.scaleToWidth(width);
img.set({
selectable: false,
lockMovementX: true,
lockMovementY: true,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
visible: this.isVisible , // 초기 visibility 설정
});
this.imageInstance = img; // 참조 저장
canvas.add(img);
});
}
toggleVisibility() {
if (this.imageInstance) {
this.isVisible = !this.isVisible;
this.imageInstance.visible = this.isVisible;
}
}
}
class ChitoImage {
url: string;
isVisible: boolean;
color: string;
imageInstance: fabric.Image | null = null; // fabric 이미지 객체를 참조하기 위한 변수
constructor(url: string, color : string ,isVisible = true) {
this.color = color
this.url = url;
this.isVisible = isVisible;
}
addToCanvas(canvas: fabric.Canvas): Promise<void> {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(this.url, (img) => {
if (!img) {
reject(new Error('Image loading failed'));
return;
}
img.scaleToWidth(width);
img.set({
selectable: false,
lockMovementX: true,
lockMovementY: true,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
visible: this.isVisible, // 초기 visibility 설정
});
const filter = new fabric.Image.filters.BlendColor({
color: this.color,
// mode: 'multiply'
});
img.filters.push(filter);
img.applyFilters();
canvas.renderAll();
this.imageInstance = img; // 참조 저장
canvas.add(img);
resolve(); // 이미지 처리가 끝났을 때 Promise를 완료함
});
});
}
updateImageColor(color) {
this.color=color
if (this.imageInstance) {
const filter = new fabric.Image.filters.BlendColor({
color: color
});
this.imageInstance.filters = [filter];
this.imageInstance.applyFilters();
canvas.renderAll();
}
}
}
$: body && body.updateImageColor(bodyColor);
$: bodyAcc && bodyAcc.updateImageColor(bodyAccColor);
$: arm && arm.updateImageColor(armColor);
$: armAcc && armAcc.updateImageColor(armAccColor);
$: skin && skin.updateImageColor(skinColor);
$: pants && pants.updateImageColor(pantsColor);
$: smallHair && smallHair.updateImageColor(smallhairColor);
$: bigHair && bigHair.updateImageColor(bighairColor);
$: eyebrow && eyebrow.updateImageColor(eyebrowColor);
const toggleIconVisibility = (icon) => {
icon.toggleVisibility();
canvas.renderAll();
}
const getWidth = () => {
if(window.innerWidth < 600) return window.innerWidth;
return 600;
}
function toggleDrawing() {
isDrawing = !isDrawing;
canvas.isDrawingMode = isDrawing;
updateBrush();
}
function draw() {
isDrawing = true;
canvas.isDrawingMode = isDrawing;
updateBrush();
}
function notDraw() {
isDrawing = false;
canvas.isDrawingMode = isDrawing;
updateBrush();
}
function updateBrush() {
if (canvas) {
canvas.freeDrawingBrush.color = penColor;
canvas.freeDrawingBrush.width = penSize;
}
}
onMount(async ()=>{
canvas = new fabric.Canvas('canvas');
width = getWidth();
canvas.setWidth(width * canvas.getZoom());
canvas.setHeight(width * canvas.getZoom());
body = new ChitoImage(BODY,bodyColor);
await body.addToCanvas(canvas)
bigHair = new ChitoImage(BIG_HAIR,bighairColor);
await bigHair.addToCanvas(canvas)
skin = new ChitoImage(SKIN,skinColor);
await skin.addToCanvas(canvas)
arm = new ChitoImage(ARM,armColor);
await arm.addToCanvas(canvas)
bodyAcc = new ChitoImage(BODY_ACC,bodyAccColor);
await bodyAcc.addToCanvas(canvas)
armAcc = new ChitoImage(ARM_ACC,armAccColor);
await armAcc.addToCanvas(canvas)
pants = new ChitoImage(PANTS,pantsColor);
await pants.addToCanvas(canvas)
smallHair = new ChitoImage(SMALL_HAIR,smallhairColor);
await smallHair.addToCanvas(canvas)
eyebrow = new ChitoImage(EYE_BROW,eyebrowColor);
await eyebrow.addToCanvas(canvas)
fabric.Image.fromURL(LINE, function(img) {
img.scaleToWidth(width);
img.selectable = false;
canvas.add(img)
airpods_max = new AccImage(AIRPODS_MAX_IMG);
airpods_max.addToCanvas(canvas);
dark_circle = new AccImage(DARK_CIRCLE_IMG);
dark_circle.addToCanvas(canvas);
glasses = new AccImage(GLASSES_IMG);
glasses.addToCanvas(canvas);
minoi_sunglasses = new AccImage(MINOI_SUNGLASSES_IMG);
minoi_sunglasses.addToCanvas(canvas);
cookies = new AccImage(COOKIE_IMG);
cookies.addToCanvas(canvas);
uniform = new AccImage(TOT_IMG);
uniform.addToCanvas(canvas);
cotton_candy = new AccImage(COTTON_CANDY_IMG);
cotton_candy.addToCanvas(canvas);
bechelor_cap = new AccImage(BECHELOR_CAP);
bechelor_cap.addToCanvas(canvas);
flushing = new AccImage(FLUSHING);
flushing.addToCanvas(canvas);
canvas.renderAll()
});
canvas.isDrawingMode = isDrawing;
updateBrush();
})
$: if(canvas){
canvas.setBackgroundColor(hex)
canvas.renderAll()
}
// 배경 이미지 설정 함수
function setBackgroundImage(event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
fabric.Image.fromURL(e.target.result.toString(), (img) => {
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height
});
});
};
reader.readAsDataURL(file);
}
// 배경 이미지 제거 함수
function removeBackgroundImage() {
canvas.setBackgroundImage(null, canvas.renderAll.bind(canvas));
backgroundImage.value = ""; // 입력 요소의 값을 초기화
}
// 그림 그리기 기능을 활성화/비활성화하는 변수
let isDrawingMode = false;
const handleAddImage = (e: Event & {
currentTarget: EventTarget & HTMLInputElement;
}) => {
const files = e.currentTarget.files;
if (!files) return;
Array.from(files).forEach((file) => {
const url = URL.createObjectURL(file);
fabric.Image.fromURL(url, function (img) {
const left = 235 / 600;
const top = 360 / 600;
img.set({
left: (left * width) + (Math.random() - 0.5) * 100, // 위치를 약간 무작위로 조절하여 겹치지 않게 함
top: (top * width) + (Math.random() - 0.5) * 100, // 위치를 약간 무작위로 조절하여 겹치지 않게 함
angle: 0,
});
img.scaleToWidth(width / 6);
canvas.add(img);
});
});
}
function removeImage() {
const activeObjects = canvas.getActiveObjects();
if (activeObjects.length) {
activeObjects.forEach(object => {
canvas.remove(object);
});
canvas.discardActiveObject().renderAll(); // 선택된 객체들을 취소하고 캔버스를 다시 렌더링
}
notDraw()
}
</script>
<main style={`width: ${width}`}>
<h1>~나만의 치토 만들기~</h1>
<canvas id="canvas" width="2400" height="2400" style="border:1px solid #ccc"></canvas>
<div class="toolbar-selector">
<button class="toggle-button" on:click={() => setToolbar("default")}>배경</button>
<button class="toggle-button" on:click={() => setToolbar("icon")}>액세사리</button>
<button class="toggle-button" on:click={() => setToolbar("clothesColor")}>의상</button>
<button class="toggle-button" on:click={() => setToolbar("chitoColor")}>치토</button>
<button class="toggle-button" on:click={() => setToolbar("draw")}>그리기</button>
<button class="toggle-button" on:click={() => setToolbar("save")}>저장</button>
<!-- 추가로 다른 툴바 버튼들을 이곳에 배치 -->
</div>
{#if currentToolbar === "default"}
<div class="toolbar">
<div>
<h2>배경색</h2>
<ColorPicker bind:hex isA11yClosable={false} label=''/>
</div>
<div>
<h2>배경 사진</h2>
<div>
<button on:click={() => backgroundImage.click()}>추가</button>
<button on:click={removeBackgroundImage}>제거</button>
</div>
<input bind:this={backgroundImage} on:change={setBackgroundImage} type="file" accept="image/*" style="display: none" />
</div>
</div>
{/if}
{#if currentToolbar === "clothesColor"}
<div class="toolbar">
<div>
<h2>몸통</h2>
<ColorPicker bind:hex={bodyColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>몸통 무늬</h2>
<ColorPicker bind:hex={bodyAccColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2></h2>
<ColorPicker bind:hex={armColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>팔 무늬</h2>
<ColorPicker bind:hex={armAccColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>바지</h2>
<ColorPicker bind:hex={pantsColor} isA11yClosable={false} label=''/>
</div>
</div>
{/if}
{#if currentToolbar === "chitoColor"}
<div class="toolbar">
<div>
<h2>피부</h2>
<ColorPicker bind:hex={skinColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>머리 바깥쪽</h2>
<ColorPicker bind:hex={bighairColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>머리 안쪽</h2>
<ColorPicker bind:hex={smallhairColor} isA11yClosable={false} label=''/>
</div>
<div>
<h2>눈썹</h2>
<ColorPicker bind:hex={eyebrowColor} isA11yClosable={false} label=''/>
</div>
</div>
{/if}
{#if currentToolbar === "icon"}
<div class="acctoolbar">
<div class="vertical-buttons">
<button on:click={()=>toggleIconVisibility(cotton_candy)}>
{'솜사탕'}
</button>
<button on:click={()=>toggleIconVisibility(dark_circle)}>
{'다크서클'}
</button>
<button on:click={()=>toggleIconVisibility(glasses)}>
{'안경'}
</button>
<button on:click={()=>toggleIconVisibility(cookies)}>
{'쿠키'}
</button>
</div>
<div class="vertical-buttons">
<button on:click={()=>toggleIconVisibility(airpods_max)}>
{'에어팟 맥스'}
</button>
<button on:click={()=>toggleIconVisibility(minoi_sunglasses)}>
{'미노이 선글라스'}
</button>
<button on:click={()=>toggleIconVisibility(uniform)}>
{'축구 유니폼'}
</button>
</div>
<div class="vertical-buttons">
<button on:click={()=>toggleIconVisibility(bechelor_cap)}>
{'학사모'}
</button>
<button on:click={()=>toggleIconVisibility(flushing)}>
{'홍조'}
</button>
</div>
<div class="vertical-buttons">
<h2>사진 추가</h2>
<div>
<button on:click={() => inputImage.click()}>추가</button>
<button on:click={removeImage}>제거</button>
</div>
<input bind:this={inputImage} on:change={handleAddImage} type="file" accept="image/*" style="display: none" />
</div>
</div>
{/if}
{#if currentToolbar === "draw"}
<div class="drawtoolbar">
<div class="drawsubtoolbar">
<h6>펜을 선택해 그림을 그릴 수 있습니다. <br>지우고 싶은 것을 선택 후하고 지우개를 누르면 지워집니다.<br><br></h6>
</div>
<!-- <button on:click={toggleDrawing}>{isDrawing ? '그리기 중지' : '그리기 시작'}</button> -->
<div class='drawsubtoolbar'>
<button on:click={notDraw} class="{isDrawing ? '' : 'selected'}">
<img src={select_icon} alt="Draw" class="icon-size" />
</button>
<button on:click={draw} class="{isDrawing ? 'selected' : ''}">
<img src={pen_icon} alt="Draw" class="icon-size" />
</button>
<button on:click={removeImage}><img src={eraser_icon} alt="Erase" class="icon-size" /></button>
</div>
<div class='drawsubtoolbar'>
<div>
<h2 >펜 색상</h2>
<input type="color" bind:value={penColor} on:change={updateBrush} id="penColor" />
</div>
<div>
<h2 >펜 크기: {penSize}</h2>
<input type="range" bind:value={penSize} min="1" max="50" on:input={updateBrush} id="penSize" />
</div>
</div>
</div>
{/if}
{#if currentToolbar === "save"}
<div class="drawtoolbar">
<div class="savesubtoolbar">
<button on:click={(e) => {
const data = canvas.toDataURL({format: 'png', quality: 1, multiplier: 4});
const link = document.createElement('a');
link.download = 'my-chito.png';
link.href = data;
link.click();
}}>저장</button>
</div>
<div class="drawsubtoolbar">
<h6>'@ajou_doit'을 태그하여 인스타에 공유하면 추첨을 통해 배달의민족 상품권을 드립니다!</h6>
</div>
</div>
{/if}
</main>
<footer style={`width: ${width}`}>
<span>
아주대학교 2023-2 웹시설
</span>
<span>
<a>카카아톡</a>
</span>
</footer>
<style>
h1{
margin: 20px 0;
}
a {
color: #0098fa;
text-decoration: none;
}
main, footer{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: rgb(56, 56, 56);
}
footer{
flex-direction: row;
gap: 10px;
font-weight: 800;
color: rgb(98, 98, 98)
}
.acctoolbar, .toolbar {
display: flex;
justify-content: center;
width: 100%;
max-width: 600px;
margin: 20px auto;
padding: 20px 0;
border: 1px solid #ccc;
border-radius: 10px;
}
.drawtoolbar {
justify-content: center;
width: 100%;
max-width: 600px;
margin: 20px auto;
padding: 20px 0;
border: 1px solid #ccc;
border-radius: 10px;
}
.drawsubtoolbar {
display: flex;
justify-content: center;
width: 100%;
max-width: 600px;
margin-bottom: 5px;
}
.savesubtoolbar{
display: flex;
justify-content: center;
width: 100%;
max-width: 600px;
margin-bottom: 30px;
}
.savesubtoolbar > button {
margin-right: 1rem;
margin-left: 1rem;
}
.toolbar > div, .drawsubtoolbar > div {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
}
.acctoolbar {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
}
button {
padding: 5px 10px;
border: 2px solid #ccc;
border-radius: 4px;
background-color: #fff;
font-weight: 700;
cursor: pointer;
}
.toggle-button:hover {
background-color: #357ABD;
}
.toggle-button {
padding: 10px 20px;
border: none;
border-radius: 20px;
background-color: #4A90E2;
color: #FFFFFF;
transition: background-color 0.3s;
cursor: pointer;
}
/* 툴바 선택자 스타일 */
.toolbar-selector {
display: flex;
gap: 10px;
margin-top: 20px;
}
.selected {
border: 2px solid blue;
border-radius: 4px;
}
.icon-size {
width: 30px; /* 원하는 너비로 설정 */
height: 30px; /* 원하는 높이로 설정 */
}
.vertical-buttons {
display: flex;
flex-direction: row;
gap: 10px;
}
.drawsubtoolbar > button {
margin-right: 10px;
margin-left: 10px;
}
@media only screen and (max-width: 768px) {
h2 {
font-size: 18px; /* 원하는 크기로 조절 */
}
/* 아이콘 크기 줄이기 */
.icon-size {
width: 15px;
height: 15px;
}
.toolbar-selector {
gap: 0.3rem;
}
.toggle-button {
padding: 0.2rem 0.5rem;
}
button {
padding: 0.2rem 0.5rem; /* 패딩 값을 조절하여 버튼의 크기를 줄임 */
font-size: 12px; /* 버튼 내부의 글자 크기 조절 */
}
}
</style>
*,
:after,
:before {
box-sizing: border-box;
}
* {
margin: 0;
}
body,
html {
height: 100%;
}
body {
-webkit-font-smoothing: antialiased;
line-height: 1.5;
margin: 0;
font-family: Arial, sans-serif;
}
canvas,
img,
picture,
svg,
video {
display: block;
max-width: 100%;
}
button,
input,
select,
textarea {
font: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
overflow-wrap: break-word;
}
#__next,
#root {
isolation: isolate;
}
src/assets/acc/airpodsmax.png

1.24 MiB

src/assets/acc/bachelorcap.png

1.08 MiB

src/assets/acc/cookies.png

518 KiB

src/assets/acc/cottoncandy.png

1.08 MiB

src/assets/acc/darkcircles.png

484 KiB

src/assets/acc/flushing.png

687 KiB

src/assets/acc/glasses.png

585 KiB

src/assets/acc/minoisunglasses.png

556 KiB

src/assets/acc/tot.png

844 KiB

src/assets/eraser.png

10.3 KiB

src/assets/myChito/arm-acc.png

11.5 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment