feat: add effects

feat: add setting types
This commit is contained in:
Simon Vieille 2025-09-08 21:31:46 +02:00
commit 8d1567a54a
Signed by: deblan
GPG key ID: 579388D585F70417
3 changed files with 177 additions and 59 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 788 KiB

Before After
Before After

View file

@ -13,8 +13,8 @@ const resultLoading = ref(false)
const availableEffects = ref([])
const effectToAdd = ref(null)
const makeSetting = (label, value, min, max, step) => {
return {label, value, min, max, step}
const makeSetting = (type, label, value, options) => {
return {type, label, value, options}
}
const openImageFile = () => {
@ -29,80 +29,127 @@ const openImageFile = () => {
availableEffects.value = [
{
label: "Crop",
name: "crop",
enabled: false,
label: "Blur",
name: "blur",
enabled: true,
settings: {
x0: makeSetting("Top left X", 0, 0, info.width-1, 1),
y0: makeSetting("Top left Y", 0, 0, info.height-1, 1),
x1: makeSetting("Bottom right X", 0, 0, info.width, 1),
y1: makeSetting("Bottom right left Y", 0, 0, info.height, 1),
}
},
{
label: "Crop center",
name: "crop-center",
enabled: false,
settings: {
width: makeSetting("Width", info.width, 0, info.width, 1),
height: makeSetting("Height", info.height, 0, info.height, 1)
}
},
{
label: "Scale width",
name: "scale-width",
enabled: false,
settings: {
value: makeSetting("Value", info.width, 1, info.width, 1),
}
},
{
label: "Scale height",
name: "scale-height",
enabled: false,
settings: {
value: makeSetting("Value", info.height, 1, info.height, 1),
}
},
{
label: "Resize",
name: "resize",
enabled: false,
settings: {
width: makeSetting("Width",info.width, 1, info.width, 1),
height: makeSetting("Height", info.height, 1, info.height, 1)
value: makeSetting("number", "Value", 0, {min: 0, max: 50, step: 0.1}),
}
},
{
label: "Brightness",
name: "brightness",
enabled: false,
enabled: true,
settings: {
value: makeSetting("Value", 0, -100, 100, 1),
value: makeSetting("number", "Value", 0, {min: -100, max: 100, step: 1}),
}
},
{
label: "Crop",
name: "crop",
enabled: true,
settings: {
x0: makeSetting("number", "Top left X", 0, {min: 0, max: info.width-1, step: 1}),
y0: makeSetting("number", "Top left Y", 0, {min: 0, max: info.height-1, step: 1}),
x1: makeSetting("number", "Bottom right X", 0, {min: 1, max: info.width, step: 1}),
y1: makeSetting("number", "Bottom right left Y", 0, {min: 1, max: info.height, step: 1}),
}
},
{
label: "Crop center",
name: "crop-center",
enabled: true,
settings: {
width: makeSetting("number", "Width", info.width, {min: 0, max: info.width, step: 1}),
height: makeSetting("number", "Height", info.height, {min: 0, max: info.height, step: 1}),
}
},
{
label: "Contrast",
name: "contrast",
enabled: false,
enabled: true,
settings: {
value: makeSetting("Value", 0, -100, 100, 1),
value: makeSetting("number", "Value", 0, {min: -100, max: 100, step: 1}),
}
},
{
label: "Flip horizontally",
name: "flip-h",
enabled: true,
settings: {}
},
{
label: "Flip vertically",
name: "flip-v",
enabled: true,
settings: {}
},
{
label: "Resize",
name: "resize",
enabled: true,
settings: {
width: makeSetting("number", "Width", info.width, {min: 1, max: info.width, step: 1}),
height: makeSetting("number", "Height", info.height, {min: 1, max: info.height, step: 1}),
}
},
{
label: "Rotate",
name: "rotate",
enabled: true,
settings: {
value: makeSetting("number", "Value", 0, {min: 0, max: 360, step: 1}),
backgroundColor: makeSetting("color", "Background", "#ffffff", {}),
}
},
{
label: "Rotate 90",
name: "rotate-90",
enabled: true,
settings: {}
},
{
label: "Rotate 180",
name: "rotate-180",
enabled: true,
settings: {}
},
{
label: "Rotate 270",
name: "rotate-270",
enabled: true,
settings: {}
},
{
label: "Saturation",
name: "saturation",
enabled: false,
enabled: true,
settings: {
value: makeSetting("Value", 0, -100, 100, 1),
}
},
{
label: "Blur",
name: "blur",
enabled: false,
label: "Scale height",
name: "scale-height",
enabled: true,
settings: {
value: makeSetting("Value", 0, 0, 50, 0.1),
value: makeSetting("Value", info.height, 1, info.height, 1),
}
},
{
label: "Scale width",
name: "scale-width",
enabled: true,
settings: {
value: makeSetting("Value", info.width, 1, info.width, 1),
}
},
{
label: "Quality",
name: "quality",
enabled: true,
settings: {
value: makeSetting("Value", 100, 0, 100, 1),
}
},
]
@ -133,7 +180,7 @@ const appliedEffects = () => {
}
for (let setting in effect.settings) {
item.settings[setting] = parseFloat(effect.settings[setting].value)
item.settings[setting] = effect.settings[setting].value
}
result.push(item)
@ -193,6 +240,12 @@ const deleteEffect = (index) => {
effects.value.splice(index, 1)
}
const getAvailableEffects = () => {
return availableEffects.value.sort((a, b) => {
return a.label > b.label ? 1 : -1
})
}
onMounted(openImageFile)
</script>
@ -224,20 +277,32 @@ onMounted(openImageFile)
</div>
<div class="effect-settings" v-if="element.enabled">
<div v-for="setting, key in element.settings" :key="element.id + key" class="effect-setting">
<input
type="range"
:min="setting.min"
:max="setting.max"
:step="setting.step"
v-model="setting.value"
>
<input
type="number"
:min="setting.min"
:max="setting.max"
v-model="setting.value"
>
{{ setting.label }}
<div class="effect-setting-label">
{{ setting.label }}
</div>
<div v-if="setting.type == 'number'" class="effect-setting-form" :class="['effect-setting-form--' + setting.type]">
<input
type="range"
:min="setting.options.min"
:max="setting.options.max"
:step="setting.options.step"
v-model="setting.value"
>
<input
type="number"
:min="setting.options.min"
:max="setting.options.max"
:step="setting.options.step"
v-model="setting.value"
>
</div>
<div v-if="setting.type == 'color'" class="effect-setting-form" :class="['effect-setting-form--' + setting.type]">
<input
type="color"
v-model="setting.value"
>
</div>
</div>
</div>
</div>
@ -247,7 +312,7 @@ onMounted(openImageFile)
<div>
<select v-model="effectToAdd">
<option
v-for="effect in availableEffects"
v-for="effect in getAvailableEffects()"
:key="effect.name"
:value="effect.name">
{{ effect.label }}
@ -257,12 +322,12 @@ onMounted(openImageFile)
Add
</button>
</div>
<button @click="applyEffects" :disabled="resultLoading" v-if="effects.length > 0">
<button @click="applyEffects" :disabled="resultLoading">
<template v-if="resultLoading">
Loading...
</template>
<template v-else>
Apply
Update result
</template>
</button>
</div>
@ -390,6 +455,7 @@ onMounted(openImageFile)
display: flex;
gap: 10px;
font-weight: bold;
margin-bottom: 5px;
&:hover {
cursor: pointer;
@ -398,11 +464,33 @@ onMounted(openImageFile)
&-setting {
padding: 5px 0;
display: flex;
width: 100%;
justify-content: space-between;
input {
vertical-align: middle;
margin-right: 8px;
}
&-label {
width: 40%;
}
&-form {
width: 60%;
display: flex;
justify-content: space-between;
&--number {
input[type="range"] {
width: 70%;
}
input[type="number"] {
width: 30%;
}
}
}
}
}

View file

@ -4,12 +4,14 @@ import (
"bytes"
"encoding/base64"
"image"
"image/color"
"image/jpeg"
_ "image/jpeg"
"image/png"
_ "image/png"
"math"
"os"
"strconv"
"github.com/disintegration/imaging"
"github.com/spf13/cast"
@ -25,8 +27,8 @@ type ImageInfo struct {
}
type ImageEffect struct {
Name string `json:"name"`
Settings map[string]float32 `json:"settings"`
Name string `json:"name"`
Settings map[string]any `json:"settings"`
}
func GetImageInfo(file string) *ImageInfo {
@ -65,6 +67,7 @@ func GetImageInfo(file string) *ImageInfo {
func ApplyFilters(src *ImageInfo, effects []ImageEffect) *ImageInfo {
base := GetImageInfo(src.Path)
result := base.Image
quality := 100
for _, effect := range effects {
if effect.Name == "scale-width" {
@ -120,6 +123,33 @@ func ApplyFilters(src *ImageInfo, effects []ImageEffect) *ImageInfo {
result = imaging.AdjustGamma(result, cast.ToFloat64(effect.Settings["value"]))
} else if effect.Name == "blur" {
result = imaging.Blur(result, cast.ToFloat64(effect.Settings["value"]))
} else if effect.Name == "flip-v" {
result = imaging.FlipV(result)
} else if effect.Name == "flip-h" {
result = imaging.FlipH(result)
} else if effect.Name == "rotate-90" {
result = imaging.Rotate90(result)
} else if effect.Name == "rotate-180" {
result = imaging.Rotate180(result)
} else if effect.Name == "rotate-270" {
result = imaging.Rotate270(result)
} else if effect.Name == "quality" {
quality = cast.ToInt(effect.Settings["value"])
} else if effect.Name == "rotate" {
values, err := strconv.ParseUint(string(effect.Settings["backgroundColor"].(string)[1:]), 16, 32)
if err == nil {
result = imaging.Rotate(
result,
cast.ToFloat64(effect.Settings["value"]),
color.RGBA{
R: uint8(values >> 16),
G: uint8((values >> 8) & 0xFF),
B: uint8(values & 0xFF),
A: 255,
},
)
}
}
}
@ -127,7 +157,7 @@ func ApplyFilters(src *ImageInfo, effects []ImageEffect) *ImageInfo {
if src.Type == "jpeg" {
err := jpeg.Encode(buff, result, &jpeg.Options{
Quality: 100,
Quality: quality,
})
if err != nil {