Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(colors): extract colors from image #73

Merged
merged 4 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"prepare": "husky install"
},
"dependencies": {
"colorthief": "^2.3.2",
"core-js": "^3.6.5",
"element-ui": "^2.15.6",
"emoticon.js": "^1.0.0",
Expand Down
6 changes: 5 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ What it if you can use more colors and more words to make wallpapers to express

![color store](https://i.loli.net/2021/11/08/tHmdewQEgbDnikR.png)

2. There are some emojis and emoticons for your to boost your imagination.
2. Extract colors from image.

![image](https://i.loli.net/2021/11/17/tmiIwyrACF51pvj.png)

3. There are some emojis and emoticons for your to boost your imagination.

![emojis](https://i.loli.net/2021/11/08/IudDlx8psqVPCwG.png)

Expand Down
103 changes: 92 additions & 11 deletions src/components/ColorPalette.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
<template>
<div class="color-palette-container">
<div @click="showColorsStore = true" v-if="colors.length === 0" style="cursor: pointer">
<el-empty description="No colors, click to add."> </el-empty>
<div
v-if="colors.length === 0"
class="color-empty-wrapper"
@mouseover="showButtons = true"
@mouseleave="showButtons = false"
>
<el-empty
description="No colors, hover to add."
:style="{
opacity: showButtons ? 0 : 1,
}"
>
</el-empty>
<div class="color-palette-btns" v-if="showButtons">
<el-button icon="el-icon-plus" type="primary" @click="showColorsStore = true" size="small"
>Color Store</el-button
>
<el-button
icon="el-icon-plus"
type="primary"
@click="showImageExtracter = true"
size="small"
>From Image</el-button
>
</div>
</div>
<div v-else class="color-palette-colors">
<span
Expand All @@ -12,15 +35,27 @@
draggable="true"
@dragstart="(e) => handleDragStart(e, color)"
></span>
<i
class="el-icon-circle-plus-outline"
@click="showColorsStore = true"
:style="{
float: 'left',
cursor: 'pointer',
lineHeight: '30px',
}"
></i>
<el-popover placement="bottom" trigger="hover">
<el-button icon="el-icon-plus" type="primary" @click="showColorsStore = true" size="small"
>Color Store</el-button
>
<el-button
icon="el-icon-plus"
type="primary"
@click="showImageExtracter = true"
size="small"
>From Image</el-button
>
<i
slot="reference"
class="el-icon-circle-plus-outline"
:style="{
float: 'left',
cursor: 'pointer',
lineHeight: '30px',
}"
></i>
</el-popover>
</div>
<el-dialog title="Color Store" width="1000px" :visible.sync="showColorsStore">
<el-tabs :value="colorStore[0].name">
Expand Down Expand Up @@ -69,11 +104,25 @@
<el-button @click="showColorsStore = false">Cancel</el-button>
</span>
</el-dialog>
<el-dialog title="Extract Colors From Image" :visible.sync="showImageExtracter">
<image-color-picker v-model="selectedImageColors" />
<span slot="footer" class="dialog-footer">
<el-button @click="handleCloseImageColorPicker">Cancel</el-button>
<el-button
type="primary"
@click="handleAddImageColors"
v-show="selectedImageColors.length !== 0"
>Add</el-button
>
</span>
</el-dialog>
</div>
</template>

<script>
import { Message } from "element-ui";
import { colorStore } from "../data/color/index";
import ImageColorPicker from "./ImageColorPicker.vue";

export default {
data() {
Expand All @@ -82,13 +131,32 @@ export default {
colorStore,
cardSize: 200,
colors: [],
showButtons: false,
showImageExtracter: false,
selectedImageColors: [],
};
},
components: {
ImageColorPicker,
},
methods: {
handleAddImageColors() {
const colors = [...this.selectedImageColors];
this.handleAddColors(colors);
this.handleCloseImageColorPicker();
},
handleCloseImageColorPicker() {
this.showImageExtracter = false;
this.selectedImageColors = [];
},
handleAddColors(colors) {
if (colors.length === 0) return;
// 过滤掉已经有的颜色
const colorSet = new Set(this.colors);
const newColors = colors.filter((d) => !colorSet.has(d));
if (colors.length !== newColors.length) {
Message.warning("Repeat colors will not be added.");
}

// 更新颜色
this.colors.push(...newColors);
Expand All @@ -109,6 +177,19 @@ export default {
padding: 5px;
}

.color-empty-wrapper {
cursor: pointer;
position: relative;
}

.color-palette-btns {
position: absolute;
display: flex;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.color-palette-container .el-dialog__header {
font-weight: bold;
}
Expand Down
129 changes: 129 additions & 0 deletions src/components/ImageColorPicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<template>
<div class="image-color-picker-container">
<image-picker v-model="imageURL" :allowOnline="false" :cacheImage="false" />
<div class="image-color-picker-colors">
<div v-if="imageColors.length" style="color: #606266; font-size: 14px">
Click to select {{ maxCount === Infinity ? "" : `up to ${maxCount} ` }}colors you want to
use.
</div>
<span
:key="color"
v-for="(color, index) in imageColors"
class="image-color-picker-color-item"
@click="() => handleClickImageColorItem(index)"
:style="{
backgroundColor: color,
borderColor: selectedImageColorIndex.indexOf(index) === -1 ? color : '#409eff',
}"
/>
</div>
</div>
</template>

<script>
import ColorThief from "colorthief";
import { Message } from "element-ui";
import ImagePicker from "./ImagePicker.vue";
import { loadImage } from "../utils/load";
import { rgbToHex } from "../utils/color";

const colorThief = new ColorThief();

export default {
props: {
maxCount: {
type: Number,
default: Infinity,
},
value: {
type: Array,
default: () => [],
},
},
components: {
ImagePicker,
},
data() {
return {
imageURL: "",
imageColors: [],
};
},
computed: {
selectedImageColorIndex: {
get() {
console.log({ value: this.value });
return this.value
.map((vc) => this.imageColors.findIndex((ic) => vc === ic))
.filter((d) => d !== -1);
},
set(newValue) {
const colors = newValue.map((index) => this.imageColors[index]);
this.$emit("input", colors);
},
},
},
watch: {
imageURL: {
immediate: true,
async handler(newValue) {
if (newValue === "") {
this.imageColors = [];
this.selectedImageColorIndex = [];
return;
}
try {
const img = await loadImage(newValue);
const colors = colorThief.getPalette(img).map((d) => rgbToHex(...d));
this.imageColors = colors;
} catch (e) {
Message.error("Extract colors from image failed!");
}
},
},
},
methods: {
handleClickImageColorItem(index) {
const i = this.selectedImageColorIndex.indexOf(index);
const newSelectedImageColorIndex = [...this.selectedImageColorIndex];
if (i === -1) {
if (this.selectedImageColorIndex.length >= this.maxCount) {
Message.error(`Only can select ${this.maxCount} colors.`);
return;
}
newSelectedImageColorIndex.push(index);
} else {
newSelectedImageColorIndex.splice(i, 1);
}
this.selectedImageColorIndex = newSelectedImageColorIndex;
},
},
};
</script>

<style >
.image-color-picker-container {
display: flex;
justify-content: flex-start;
width: 100%;
align-items: center;
}

.image-color-picker-colors {
width: calc(100% - 250px);
text-align: start;
margin: 0 30px;
}

.image-color-picker-color-item {
display: inline-block;
width: 46px;
height: 46px;
margin-right: 15px;
margin-top: 15px;
border-radius: 5px;
cursor: pointer;
border-width: 4px;
border-style: solid;
}
</style>
34 changes: 30 additions & 4 deletions src/components/ImagePicker.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<el-upload
class="image-uploader"
:class="['image-uploader', { 'image-height': imageURL === '' }]"
action=""
:auto-upload="false"
:on-change="handleChange"
Expand All @@ -11,12 +11,14 @@
<img
:src="imageURL"
class="image"
@mouseenter="hover = true"
alt="invalid url"
@mouseenter="hover = true"
@error="handleErrorImage"
v-if="imageURL !== ''"
/>
<i v-else class="el-icon-plus image-uploader-icon" @mouseenter="hover = true"></i>
<div
v-if="hover"
v-if="hover && allowOnline"
class="image-overlayer"
@mouseleave="hover = false"
@click="handleClickOverlayer"
Expand All @@ -27,7 +29,7 @@
>
</div>
</el-upload>
<el-dialog title="Upload online image" :visible.sync="showUploadDialog">
<el-dialog title="Upload online image" :visible.sync="showUploadDialog" append-to-body>
<el-input v-model="onelineImageURL" />
<span slot="footer" class="dialog-footer">
<el-button @click="showUploadDialog = false">Cancel</el-button>
Expand All @@ -47,6 +49,14 @@ export default {
},
props: {
imageURL: String,
cacheImage: {
type: Boolean,
default: true,
},
allowOnline: {
type: Boolean,
default: true,
},
},
data() {
return {
Expand All @@ -71,6 +81,7 @@ export default {
handleRemove() {
this.uploaded = false;
this.hover = false;
if (!this.cacheImage) this.$emit("change", "");
},
handleUploadOnline(e) {
e.stopPropagation();
Expand Down Expand Up @@ -103,6 +114,20 @@ export default {
<style>
.image-uploader {
width: 250px;
min-height: 10px;
}

.image-height {
height: 250px;
}

.image-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 250px;
height: 250px;
line-height: 250px;
text-align: center;
}

.image-uploader .el-upload {
Expand All @@ -115,6 +140,7 @@ export default {
flex-direction: column;
align-items: flex-start;
width: 100%;
height: 100%;
}

.image-uploader .el-upload:hover {
Expand Down
2 changes: 1 addition & 1 deletion src/data/color/color-hunt.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const colorHunt = {
link: "https://colorhunt.co/palette/28df9999f3bdd2f6c5f6f7d4",
},
{
values: ["#2D4059", "#2D4059", "#F07B3F", "#FFD460"],
values: ["#2D4059", "#ea5455", "#F07B3F", "#FFD460"],
link: "https://colorhunt.co/palette/2d4059ea5455f07b3fffd460",
},
{
Expand Down
Loading