mirror of
https://github.com/igorski/bitmappery.git
synced 2026-06-16 19:25:38 +02:00
Implement color picker and brush color selection
This commit is contained in:
15
README.md
15
README.md
@@ -6,6 +6,18 @@ No, I'm building a tool that does the bare minimum what I require and what I don
|
||||
find in other open source tools. That doesn't mean of course that contributions
|
||||
related to Photoshop-esque features aren't welcomed.
|
||||
|
||||
### All self-written ?
|
||||
|
||||
Yep, though it helps having worked five years in the photo software industry and having
|
||||
tackled problems before.
|
||||
|
||||
PhotoMound does however make use of the following excellent libraries to speed up its development:
|
||||
|
||||
* [Vue](https://github.com/vuejs/vue) with [Vuex](https://github.com/vuejs/vuex) and [VueI18n](https://github.com/kazupon/vue-i18n)
|
||||
* [Pickr](https://github.com/Simonwep/pickr) by Simonwep
|
||||
* [Vue slider component](https://github.com/NightCatSama/vue-slider-component) by NightCatSama
|
||||
* [Vue search select](https://github.com/moreta/vue-search-select#readme) by Moreta
|
||||
|
||||
## Dropbox integration
|
||||
|
||||
Requires you to [register a client id or access token](https://www.dropbox.com/developers/apps).
|
||||
@@ -40,7 +52,7 @@ npm run lint
|
||||
* Unit tests for factories
|
||||
* DrawableLayer should only draw when brush too lis active
|
||||
* Canvas clearRect() is not doing full width and height ?
|
||||
* Layer view in options-panel: allow naming, repositioning, toggle visibility, set as mask
|
||||
* Layer view in options-panel: allow naming, repositioning, toggle visibility, change type (for masking), opacity
|
||||
* Canvas util : store transparency of images
|
||||
* Restored base64 images should be treated as binary once more (see layer-factory)
|
||||
* Add brush options > size, transparency
|
||||
@@ -53,6 +65,7 @@ npm run lint
|
||||
* Export output to image file (by rendering document content on separate canvas instead of taking snapshot at incorrect scale)
|
||||
* Load/save documents from/to Dropbox
|
||||
* Use hand cursor when draggable
|
||||
* Text editing using Google fonts!
|
||||
* Use paint brush cursor when painting
|
||||
* Add tools for layer rotation and scaling
|
||||
* Implement clone brush
|
||||
|
||||
29
package-lock.json
generated
29
package-lock.json
generated
@@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@simonwep/pickr": "^1.8.0",
|
||||
"canvas": "^2.6.1",
|
||||
"core-js": "^3.6.5",
|
||||
"dropbox": "^8.2.0",
|
||||
@@ -1420,6 +1421,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@simonwep/pickr": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.0.tgz",
|
||||
"integrity": "sha512-VaSD7TwktOsro5nQ/FjRx5JAJ09k5CNfGRHacgVRxeVPolUQwelz1SjL8HAOKZwTSmcnIObptpHABQS4zgN7sw==",
|
||||
"dependencies": {
|
||||
"core-js": "^3.8.0",
|
||||
"nanopop": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@soda/friendly-errors-webpack-plugin": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
|
||||
@@ -11736,6 +11746,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanopop": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.1.0.tgz",
|
||||
"integrity": "sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw=="
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
@@ -19730,6 +19745,15 @@
|
||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@simonwep/pickr": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.0.tgz",
|
||||
"integrity": "sha512-VaSD7TwktOsro5nQ/FjRx5JAJ09k5CNfGRHacgVRxeVPolUQwelz1SjL8HAOKZwTSmcnIObptpHABQS4zgN7sw==",
|
||||
"requires": {
|
||||
"core-js": "^3.8.0",
|
||||
"nanopop": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"@soda/friendly-errors-webpack-plugin": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz",
|
||||
@@ -28488,6 +28512,11 @@
|
||||
"to-regex": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"nanopop": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.1.0.tgz",
|
||||
"integrity": "sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvas": "^2.6.1",
|
||||
"@simonwep/pickr": "^1.8.0",
|
||||
"core-js": "^3.6.5",
|
||||
"dropbox": "^8.2.0",
|
||||
"register-service-worker": "^1.7.1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"en-US": {
|
||||
"brush": "Brush"
|
||||
"brush": "Brush",
|
||||
"brushColor": "Brush color"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,56 @@
|
||||
<template>
|
||||
<div class="tool-option">
|
||||
<h3 v-t="'brush'"></h3>
|
||||
<div class="wrapper input">
|
||||
<label v-t="'brushColor'"></label>
|
||||
<component
|
||||
:is="colorPicker"
|
||||
v-model="brushColor"
|
||||
class="color-picker"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import ToolTypes from "@/definitions/tool-types";
|
||||
import DrawableLayer from "@/components/ui/zcanvas/drawable-layer";
|
||||
import { runSpriteFn } from "@/factories/sprite-factory";
|
||||
import messages from "./messages.json";
|
||||
|
||||
export default {
|
||||
i18n: { messages },
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"brushOptions",
|
||||
]),
|
||||
colorPicker() {
|
||||
return () => import( "@/components/ui/color-picker/color-picker" );
|
||||
},
|
||||
brushColor: {
|
||||
get() {
|
||||
return this.brushOptions.color;
|
||||
},
|
||||
set( value ) {
|
||||
this.setToolOptionValue({
|
||||
tool: ToolTypes.BRUSH,
|
||||
option: "color",
|
||||
value,
|
||||
});
|
||||
runSpriteFn( sprite => {
|
||||
if ( sprite instanceof DrawableLayer ) {
|
||||
sprite.cacheGradient( this.brushColor );
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
"setToolOptionValue",
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
93
src/components/ui/color-picker/color-picker.vue
Normal file
93
src/components/ui/color-picker/color-picker.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020 - https://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
<template>
|
||||
<div class="color-picker">
|
||||
<div ref="picker"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "@simonwep/pickr/dist/themes/nano.min.css";
|
||||
import Pickr from "@simonwep/pickr/dist/pickr.es5.min"; // 3 x size of modern bundle
|
||||
//import Pickr from "@simonwep/pickr";
|
||||
|
||||
let pickrInstance;
|
||||
|
||||
const FRAC_VALUE = 0;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// the value is represented as RGBA
|
||||
value: {
|
||||
type: String,
|
||||
default: "rgba(255,255,255,1)",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
pickrInstance = Pickr.create({
|
||||
el: this.$refs.picker,
|
||||
theme: "nano",
|
||||
default: this.value,
|
||||
defaultRepresentation: "HEX",
|
||||
components: {
|
||||
preview: true,
|
||||
opacity: true,
|
||||
hue: true,
|
||||
interaction: {
|
||||
rgba: true,
|
||||
hex: true,
|
||||
cmyk: true,
|
||||
input: true,
|
||||
save: true
|
||||
}
|
||||
}
|
||||
});
|
||||
pickrInstance.on( "save", this.saveColor.bind( this ));
|
||||
},
|
||||
destroy() {
|
||||
pickrInstance?.destroyAndRemove();
|
||||
},
|
||||
methods: {
|
||||
saveColor( value ) {
|
||||
const rgba = value.toRGBA();
|
||||
|
||||
const red = rgba[ 0 ].toFixed( FRAC_VALUE );
|
||||
const green = rgba[ 1 ].toFixed( FRAC_VALUE );
|
||||
const blue = rgba[ 2 ].toFixed( FRAC_VALUE );
|
||||
const alpha = rgba[ 3 ];
|
||||
|
||||
this.$emit( "input", `rgba(${red},${green},${blue},${alpha})` );
|
||||
pickrInstance.hide();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/_mixins";
|
||||
|
||||
.color-picker {
|
||||
display: inline-block;
|
||||
margin-top: -$spacing-small;
|
||||
}
|
||||
</style>
|
||||
@@ -41,35 +41,43 @@ function DrawableLayer( layer ) {
|
||||
this.setDraggable( true );
|
||||
|
||||
// TODO: setters and cache for these
|
||||
const radius = 30; // TODO: setter
|
||||
const innerRadius = 5;
|
||||
const outerRadius = 70;
|
||||
const opacity = .5; // 0 - 1 range
|
||||
ctx.globalAlpha = opacity;
|
||||
|
||||
const { cvs: brush, ctx: brushCtx } = createCanvas( radius * 2, radius * 2 );
|
||||
this.halfRadius = 0;
|
||||
|
||||
// Radii of the white glow.
|
||||
// Radius of the entire circle.
|
||||
const { cvs: brush, ctx: brushCtx } = createCanvas( 10, 10 );
|
||||
|
||||
x = radius;
|
||||
y = radius;
|
||||
const gradient = brushCtx.createRadialGradient( x, y, innerRadius, x, y, outerRadius );
|
||||
gradient.addColorStop( 0, 'rgba(255,0,0,1)' );
|
||||
gradient.addColorStop( 1, 'rgba(255,255,255,1)' );
|
||||
this.cacheGradient = function( color, radius = 30, innerRadius = 5, outerRadius = 70 )
|
||||
{
|
||||
x = radius;
|
||||
y = radius;
|
||||
|
||||
brushCtx.arc( x, y, radius, 0, 2 * Math.PI );
|
||||
// update brush Canvas size
|
||||
brush.width = radius * 2;
|
||||
brush.height = radius * 2;
|
||||
|
||||
brushCtx.fillStyle = gradient;
|
||||
brushCtx.fill();
|
||||
const gradient = brushCtx.createRadialGradient( x, y, innerRadius, x, y, outerRadius );
|
||||
gradient.addColorStop( 0, color );
|
||||
gradient.addColorStop( 1, 'rgba(255,255,255,1)' );
|
||||
|
||||
this.handleMove = function( x, y ) {
|
||||
brushCtx.arc( x, y, radius, 0, 2 * Math.PI );
|
||||
brushCtx.fillStyle = gradient;
|
||||
brushCtx.fill();
|
||||
|
||||
this.halfRadius = radius / 2;
|
||||
},
|
||||
|
||||
this.handleMove = function( x, y )
|
||||
{
|
||||
// TODO: this zoomFactor should be taken into account by handleInteraction of zCanvas !!
|
||||
// BECAUSE IT DOES NOT WORK FOR HANDLEPRESS CURRENTLY
|
||||
x /= this.canvas.zoomFactor;
|
||||
y /= this.canvas.zoomFactor;
|
||||
ctx.drawImage( brush, x - radius / 2, y - radius / 2 );
|
||||
ctx.drawImage( brush, x - this.halfRadius, y - this.halfRadius );
|
||||
}
|
||||
|
||||
this.cacheGradient( "rgba(255,0,0,1)" );
|
||||
}
|
||||
sprite.extend( DrawableLayer );
|
||||
export default DrawableLayer;
|
||||
|
||||
@@ -28,12 +28,13 @@ export default {
|
||||
activeTool: null,
|
||||
options: {
|
||||
[ ToolTypes.ZOOM ] : { level: 1 },
|
||||
[ ToolTypes.BRUSH ]: { },
|
||||
[ ToolTypes.BRUSH ]: { color: "rgba(255,0,0,1)" },
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
activeTool: state => state.activeTool,
|
||||
zoomOptions: state => state.options[ ToolTypes.ZOOM ],
|
||||
brushOptions: state => state.options[ ToolTypes.BRUSH ],
|
||||
},
|
||||
mutations: {
|
||||
setActiveTool( state, tool ) {
|
||||
|
||||
Reference in New Issue
Block a user