seekia/gui/imageGui.go

677 lines
23 KiB
Go
Raw Permalink Normal View History

package gui
// imageGui.go implements an image editor and pages to view images
import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/widget"
import "fyne.io/fyne/v2/theme"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/canvas"
import "fyne.io/fyne/v2/layout"
import "seekia/internal/imagery"
import "seekia/internal/helpers"
import "seekia/internal/imageEffects"
import "image"
import "errors"
func setViewFullpageImagePage(window fyne.Window, inputImage image.Image, previousPage func()){
title := getPageTitleCentered("Viewing Image")
backButton := getBackButtonCentered(previousPage)
header := container.NewVBox(title, backButton, widget.NewSeparator())
//TODO: Add a right-click to save ability
// This is useful so users/moderators can perform reverse-image searching to detect fake profiles
fyneImage := canvas.NewImageFromImage(inputImage)
fyneImage.FillMode = canvas.ImageFillContain
page := container.NewBorder(header, nil, nil, nil, fyneImage)
setPageContent(page, window)
}
func setViewFullpageImagesWithNavigationPage(window fyne.Window, inputImagesList []image.Image, imageIndex int, previousPage func()){
title := getPageTitleCentered("Viewing Image")
backButton := getBackButtonCentered(previousPage)
header := container.NewVBox(title, backButton, widget.NewSeparator())
numberOfImages := len(inputImagesList)
if (numberOfImages == 0){
setErrorEncounteredPage(window, errors.New("setViewFullpageImagesWithNavigationPage called with empty images list"), previousPage)
return
}
finalIndex := numberOfImages - 1
getCurrentImageIndex := func()int{
if (imageIndex > finalIndex){
return finalIndex
}
if (imageIndex < 0){
return 0
}
return imageIndex
}
currentIndex := getCurrentImageIndex()
//TODO: Add a right-click to save ability
// This is useful so users/moderators can perform reverse-image searching to detect fake profiles
currentImage := inputImagesList[currentIndex]
currentFyneImage := canvas.NewImageFromImage(currentImage)
currentFyneImage.FillMode = canvas.ImageFillContain
if (numberOfImages == 1){
page := container.NewBorder(header, nil, nil, nil, currentFyneImage)
setPageContent(page, window)
return
}
getPreviousImageButton := func()fyne.Widget{
if (currentIndex == 0){
emptyButton := widget.NewButton("", nil)
return emptyButton
}
previousButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){
setViewFullpageImagesWithNavigationPage(window, inputImagesList, currentIndex-1, previousPage)
})
return previousButton
}
previousImageButton := getPreviousImageButton()
getNextImageButton := func()fyne.Widget{
if (currentIndex == finalIndex){
emptyButton := widget.NewButton("", nil)
return emptyButton
}
nextButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){
setViewFullpageImagesWithNavigationPage(window, inputImagesList, currentIndex+1, previousPage)
})
return nextButton
}
nextImageButton := getNextImageButton()
navigationButtonsRow := getContainerCentered(container.NewGridWithRows(1, previousImageButton, nextImageButton))
page := container.NewBorder(header, navigationButtonsRow, nil, nil, currentFyneImage)
setPageContent(page, window)
}
func setSlowlyRevealImagePage(window fyne.Window, inputImage image.Image, percentageRevealedInt int, previousPage func()){
currentPage := func(){setSlowlyRevealImagePage(window, inputImage, percentageRevealedInt, previousPage)}
backButton := getBackButtonCentered(previousPage)
if (percentageRevealedInt >= 100){
title := getPageTitleCentered("Viewing Image")
header := container.NewVBox(title, backButton, widget.NewSeparator())
fyneImageObject := canvas.NewImageFromImage(inputImage)
fyneImageObject.FillMode = canvas.ImageFillContain
page := container.NewBorder(header, nil, nil, nil, fyneImageObject)
setPageContent(page, window)
return
}
title := getPageTitleCentered("Reveal Image")
description := widget.NewLabel("Slowly reveal the image.")
revealSettingsButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
setManagePixelateImagesSettingPage(window, currentPage)
})
descriptionRow := container.NewHBox(layout.NewSpacer(), description, revealSettingsButton, layout.NewSpacer())
invert0to100 := func(input0to100 int)int{
if (input0to100 < 0) {
return 100
}
inverted := 100 - input0to100
return inverted
}
currentAmountToPixelate := invert0to100(percentageRevealedInt)
currentPixelatedImage, err := imagery.PixelateGolangImage(inputImage, currentAmountToPixelate)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
imageSize := getCustomFyneSize(100)
imageObject := canvas.NewImageFromImage(currentPixelatedImage)
imageObject.FillMode = canvas.ImageFillContain
imageObject.SetMinSize(imageSize)
imageCentered := getFyneImageCentered(imageObject)
percentageRevealedString := helpers.ConvertIntToString(percentageRevealedInt)
percentageRevealedLabel := getBoldLabelCentered(percentageRevealedString + "% revealed")
buttonsGrid := container.NewGridWithColumns(1)
if (percentageRevealedInt <= 90){
reveal10PercentButton := widget.NewButton("Reveal 10%", func(){
amountToPixelate := invert0to100(percentageRevealedInt + 10)
newImage, err := imagery.PixelateGolangImage(inputImage, amountToPixelate)
if (err != nil) {
setErrorEncounteredPage(window, err, currentPage)
return
}
imageObject.Image = newImage
imageObject.Refresh()
setSlowlyRevealImagePage(window, inputImage, percentageRevealedInt + 10, previousPage)
})
buttonsGrid.Add(reveal10PercentButton)
}
if (percentageRevealedInt <= 99){
reveal1PercentButton := widget.NewButton("Reveal 1%", func(){
amountToPixelate := invert0to100(percentageRevealedInt + 1)
newImage, err := imagery.PixelateGolangImage(inputImage, amountToPixelate)
if (err != nil) {
setErrorEncounteredPage(window, err, currentPage)
return
}
imageObject.Image = newImage
imageObject.Refresh()
setSlowlyRevealImagePage(window, inputImage, percentageRevealedInt + 1, previousPage)
})
buttonsGrid.Add(reveal1PercentButton)
}
if (percentageRevealedInt < 100){
revealButton := widget.NewButtonWithIcon("Reveal", theme.VisibilityIcon(), func(){
setSlowlyRevealImagePage(window, inputImage, 100, previousPage)
})
buttonsGrid.Add(revealButton)
}
buttonsGridCentered := getContainerCentered(buttonsGrid)
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), imageCentered, percentageRevealedLabel, buttonsGridCentered)
setPageContent(page, window)
}
// This function provides an image editor to perform image effects
// PreviousState is a function to return to the image as it was before the current filter/effect was added
func setEditImagePage(window fyne.Window, originalImage image.Image, previousStateExists bool, previousState func(), currentImage image.Image, previousPage func(), setSubmitImagePageFunction func(image.Image, func())){
if (originalImage == nil || currentImage == nil){
setErrorEncounteredPage(window, errors.New("setEditImagePage called with nil image(s)."), previousPage)
return
}
currentPage := func(){setEditImagePage(window, originalImage, previousStateExists, previousState, currentImage, previousPage, setSubmitImagePageFunction)}
title := getPageTitleCentered("Apply Image Effects")
backButton := getBackButtonCentered(previousPage)
page := container.NewVBox(title, backButton, widget.NewSeparator())
if (previousStateExists == true){
revertChangesButton := widget.NewButtonWithIcon("Revert All Changes", theme.ContentClearIcon(), func(){
//TODO: Add Are you sure dialog.
setEditImagePage(window, originalImage, false, nil, originalImage, previousPage, setSubmitImagePageFunction)
})
undoChangesButton := widget.NewButtonWithIcon("Undo", theme.ContentUndoIcon(), func(){
previousState()
})
undoRevertButtonsRow := getContainerCentered(container.NewGridWithRows(1, revertChangesButton, undoChangesButton))
page.Add(undoRevertButtonsRow)
page.Add(widget.NewSeparator())
}
currentImageFyne := canvas.NewImageFromImage(currentImage)
currentImageFyne.FillMode = canvas.ImageFillContain
currentImageFyne.SetMinSize(getCustomFyneSize(70))
currentImageCentered := container.NewHBox(layout.NewSpacer(), currentImageFyne, layout.NewSpacer())
viewFullpageButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
setViewFullpageImagePage(window, currentImage, currentPage)
}))
page.Add(currentImageCentered)
page.Add(viewFullpageButton)
page.Add(widget.NewSeparator())
submitButton := getWidgetCentered(widget.NewButtonWithIcon("Submit Image", theme.NavigateNextIcon(), func(){
setSubmitImagePageFunction(currentImage, currentPage)
}))
page.Add(submitButton)
page.Add(widget.NewSeparator())
effectsLabel := getBoldLabelCentered("Effects:")
page.Add(effectsLabel)
overlayEmojiFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
setApplyImageEffectPage_OverlayEmoji(window, currentImage, currentImage, false, nil, 50, 50, 50, currentPage, nextPageFunction)
}
cartoonEffectFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
cartoonEffectFunction := imageEffects.ApplyCartoonEffect
setApplyAnyImageEffectPage(window, "Cartoon", 10, cartoonEffectFunction, currentImage, false, nil, currentPage, nextPageFunction)
}
pencilEffectFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
pencilEffectFunction := imageEffects.ApplyPencilEffect
setApplyAnyImageEffectPage(window, "Pencil", 10, pencilEffectFunction, currentImage, false, nil, currentPage, nextPageFunction)
}
oilPaintingEffectFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
oilPaintingEffectFunction := imageEffects.ApplyOilPaintingEffect
setApplyAnyImageEffectPage(window, "Oil Painting", 10, oilPaintingEffectFunction, currentImage, false, nil, currentPage, nextPageFunction)
}
wireframeEffectFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
wireframeEffectFunction := imageEffects.ApplyWireframeEffect
setApplyAnyImageEffectPage(window, "Wireframe", 10, wireframeEffectFunction, currentImage, false, nil, currentPage, nextPageFunction)
}
strokeEffectFunction := func(){
nextPageFunction := func(newImage image.Image){
setEditImagePage(window, originalImage, true, currentPage, newImage, previousPage, setSubmitImagePageFunction)
}
strokeEffectFunction := imageEffects.ApplyStrokeEffect
setApplyAnyImageEffectPage(window, "Stroke", 10, strokeEffectFunction, currentImage, false, nil, currentPage, nextPageFunction)
}
overlayEmojiButton := widget.NewButton("Overlay Emoji", overlayEmojiFunction)
cartoonButton := widget.NewButton("Cartoon", cartoonEffectFunction)
pencilButton := widget.NewButton("Pencil", pencilEffectFunction)
oilPaintingButton := widget.NewButton("Oil Painting", oilPaintingEffectFunction)
wireframeButton := widget.NewButton("Wireframe", wireframeEffectFunction)
strokeButton := widget.NewButton("Stroke", strokeEffectFunction)
effectButtonsGrid := container.NewGridWithColumns(3, overlayEmojiButton, cartoonButton, pencilButton, oilPaintingButton, wireframeButton, strokeButton)
effectButtonsGridCentered := getContainerCentered(effectButtonsGrid)
page.Add(effectButtonsGridCentered)
setPageContent(page, window)
}
func setApplyAnyImageEffectPage(window fyne.Window, effectTitle string, inputEffectStrength int, effectFunction func(image.Image, int)(image.Image, error), originalImage image.Image, effectedImageReady bool, effectedImage image.Image, previousPage func(), nextPage func(image.Image)){
if (originalImage == nil){
setErrorEncounteredPage(window, errors.New("setApplyAnyImageEffectPage called with nil image"), previousPage)
return
}
getCurrentEffectStrength := func()int{
if (inputEffectStrength <= 0){
return 0
}
if (inputEffectStrength >= 100){
return 100
}
return inputEffectStrength
}
effectStrength := getCurrentEffectStrength()
if (effectedImageReady == false){
setLoadingScreen(window, "Apply " + effectTitle + " Effect", "Applying " + effectTitle + " Effect")
getImageWithEffect := func()(image.Image, error){
if (effectStrength == 0){
return originalImage, nil
}
imageWithEffect, err := effectFunction(originalImage, effectStrength)
if (err != nil) { return nil, err }
return imageWithEffect, nil
}
imageWithEffect, err := getImageWithEffect()
if (err != nil) {
setErrorEncounteredPage(window, errors.New("Unable to apply image effect: " + err.Error()), previousPage)
return
}
setApplyAnyImageEffectPage(window, effectTitle, inputEffectStrength, effectFunction, originalImage, true, imageWithEffect, previousPage, nextPage)
return
}
currentPage := func(){setApplyAnyImageEffectPage(window, effectTitle, effectStrength, effectFunction, originalImage, true, effectedImage, previousPage, nextPage)}
title := getPageTitleCentered("Apply " + effectTitle + " Effect")
backButton := getBackButtonCentered(previousPage)
imageSizeFyne := getCustomFyneSize(100)
currentImageFyne := canvas.NewImageFromImage(effectedImage)
currentImageFyne.FillMode = canvas.ImageFillContain
currentImageFyne.SetMinSize(imageSizeFyne)
currentImageCentered := container.NewHBox(layout.NewSpacer(), currentImageFyne, layout.NewSpacer())
viewFullpageButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
setViewFullpageImagePage(window, effectedImage, currentPage)
}))
applyEffectFunction := func(newEffectStrength int){
setApplyAnyImageEffectPage(window, effectTitle, newEffectStrength, effectFunction, originalImage, false, nil, previousPage, nextPage)
}
getIncreaseDecreaseButtonsRow := func()*fyne.Container{
getIncrease1Button := func()fyne.Widget{
if (effectStrength == 100) {
return widget.NewButton("", nil)
}
increaseEffectButton := widget.NewButtonWithIcon("+1", theme.MoveUpIcon(), func(){
applyEffectFunction(effectStrength + 1)
})
return increaseEffectButton
}
getDecrease1Button := func()fyne.Widget{
if (effectStrength == 0) {
return widget.NewButton("", nil)
}
decreaseEffectButton := widget.NewButtonWithIcon("-1", theme.MoveDownIcon(), func(){
applyEffectFunction(effectStrength-1)
})
return decreaseEffectButton
}
getIncrease10Button := func()fyne.Widget{
if (effectStrength > 90) {
return widget.NewButton("", nil)
}
increaseEffectButton := widget.NewButtonWithIcon("+10", theme.MoveUpIcon(), func(){
if (effectStrength >= 90){
applyEffectFunction(100)
return
}
applyEffectFunction(effectStrength + 10)
})
return increaseEffectButton
}
getDecrease10Button := func()fyne.Widget{
if (effectStrength < 10) {
return widget.NewButton("", nil)
}
decreaseEffectButton := widget.NewButtonWithIcon("-10", theme.MoveDownIcon(), func(){
applyEffectFunction(effectStrength - 10)
})
return decreaseEffectButton
}
increase1Button := getIncrease1Button()
decrease1Button := getDecrease1Button()
increase10Button := getIncrease10Button()
decrease10Button := getDecrease10Button()
buttonsRow := getContainerBoxed(container.NewGridWithColumns(2, increase1Button, increase10Button, decrease1Button, decrease10Button))
return buttonsRow
}
increaseDecreaseButtonsRow := getIncreaseDecreaseButtonsRow()
effectStrengthTitle := getBoldLabelCentered("Strength:")
currentEffectStrengthString := helpers.ConvertIntToString(effectStrength)
currentEffectStrengthLabel := getWidgetCentered(getBoldLabel(currentEffectStrengthString))
effectIncreaseDecreaseButtonsWithLabel := container.NewVBox(effectStrengthTitle, currentEffectStrengthLabel, increaseDecreaseButtonsRow)
effectIncreaseDecreaseSection := getContainerBoxed(getContainerCentered(effectIncreaseDecreaseButtonsWithLabel))
submitButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm Changes", theme.ConfirmIcon(), func(){
nextPage(effectedImage)
}))
page := container.NewVBox(title, backButton, widget.NewSeparator(), currentImageCentered, viewFullpageButton, widget.NewSeparator(), submitButton, effectIncreaseDecreaseSection)
setPageContent(page, window)
}
func setApplyImageEffectPage_OverlayEmoji(window fyne.Window, originalImage image.Image, currentImage image.Image, emojiIsChosen bool, emojiImage image.Image, emojiScalePercentage int, xAxisPercentage int, yAxisPercentage int, previousPage func(), nextPage func(image.Image)){
currentPage := func(){setApplyImageEffectPage_OverlayEmoji(window, originalImage, currentImage, emojiIsChosen, emojiImage, emojiScalePercentage, xAxisPercentage, yAxisPercentage, previousPage, nextPage)}
title := getPageTitleCentered("Overlay Emoji")
backButton := getBackButtonCentered(previousPage)
setChooseEmojiPageFunction := func(){
submitEmojiFunction := func(emojiIdentifier int){
emojiGolangImage, err := getEmojiImageObject(emojiIdentifier)
if (err != nil) {
setErrorEncounteredPage(window, err, currentPage)
return
}
newImage, err := imageEffects.GetImageWithEmojiOverlay(originalImage, emojiGolangImage, 50, 50, 50)
if (err != nil) {
setErrorEncounteredPage(window, err, currentPage)
return
}
setApplyImageEffectPage_OverlayEmoji(window, originalImage, newImage, true, emojiGolangImage, 50, 50, 50, previousPage, nextPage)
}
setChooseEmojiPage(window, "Choose Emoji", "Circle Face", 0, currentPage, submitEmojiFunction)
}
if (emojiIsChosen == false){
description1 := getBoldLabelCentered("This tool enables you to overlay an emoji onto your image.")
description2 := getLabelCentered("You must first choose an emoji.")
chooseEmojiButton := getWidgetCentered(widget.NewButtonWithIcon("Choose Emoji", theme.NavigateNextIcon(), setChooseEmojiPageFunction))
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, chooseEmojiButton)
setPageContent(page, window)
return
}
imageSizeFyne := getCustomFyneSize(100)
currentImageFyne := canvas.NewImageFromImage(currentImage)
currentImageFyne.FillMode = canvas.ImageFillContain
currentImageFyne.SetMinSize(imageSizeFyne)
currentImageCentered := container.NewHBox(layout.NewSpacer(), currentImageFyne, layout.NewSpacer())
viewFullpageButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
setViewFullpageImagePage(window, currentImage, currentPage)
}))
applyImageChangeFunction := func(newEmojiScalePercentage int, newXAxisPercentage int, newYAxisPercentage int){
newImage, err := imageEffects.GetImageWithEmojiOverlay(originalImage, emojiImage, newEmojiScalePercentage, newXAxisPercentage, newYAxisPercentage)
if (err != nil) {
setErrorEncounteredPage(window, err, currentPage)
return
}
setApplyImageEffectPage_OverlayEmoji(window, originalImage, newImage, true, emojiImage, newEmojiScalePercentage, newXAxisPercentage, newYAxisPercentage, previousPage, nextPage)
}
getMoveEmojiButtons := func()*fyne.Container{
moveUpFunction := func(){
newYAxisPercentage := yAxisPercentage + 3
if (newYAxisPercentage > 100) {
newYAxisPercentage = 100
}
applyImageChangeFunction(emojiScalePercentage, xAxisPercentage, newYAxisPercentage)
}
moveDownFunction := func(){
newYAxisPercentage := yAxisPercentage - 3
if (newYAxisPercentage < 0) {
newYAxisPercentage = 0
}
applyImageChangeFunction(emojiScalePercentage, xAxisPercentage, newYAxisPercentage)
}
moveLeftFunction := func(){
newXAxisPercentage := xAxisPercentage - 3
if (newXAxisPercentage < 0) {
newXAxisPercentage = 0
}
applyImageChangeFunction(emojiScalePercentage, newXAxisPercentage, yAxisPercentage)
}
moveRightFunction := func(){
newXAxisPercentage := xAxisPercentage + 3
if (newXAxisPercentage > 100) {
newXAxisPercentage = 100
}
applyImageChangeFunction(emojiScalePercentage, newXAxisPercentage, yAxisPercentage)
}
moveUpButton := widget.NewButtonWithIcon("", theme.MoveUpIcon(), moveUpFunction)
moveDownButton := widget.NewButtonWithIcon("", theme.MoveDownIcon(), moveDownFunction)
moveLeftButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), moveLeftFunction)
moveRightButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), moveRightFunction)
buttonsGrid := container.NewGridWithColumns(3, widget.NewLabel(""), moveUpButton, widget.NewLabel(""), moveLeftButton, widget.NewLabel(""), moveRightButton, widget.NewLabel(""), moveDownButton, widget.NewLabel(""))
buttonsGridBoxed := getContainerBoxed(buttonsGrid)
return buttonsGridBoxed
}
getScaleEmojiButtons := func()*fyne.Container{
scaleUpFunction := func(){
if (emojiScalePercentage >= 97) {
applyImageChangeFunction(100, xAxisPercentage, yAxisPercentage)
return
}
newScalePercentage := emojiScalePercentage + 3
applyImageChangeFunction(newScalePercentage, xAxisPercentage, yAxisPercentage)
}
scaleDownFunction := func(){
if (emojiScalePercentage <= 10) {
applyImageChangeFunction(7, xAxisPercentage, yAxisPercentage)
return
}
newScalePercentage := emojiScalePercentage - 3
applyImageChangeFunction(newScalePercentage, xAxisPercentage, yAxisPercentage)
}
scaleUpButton := widget.NewButtonWithIcon("", theme.ContentAddIcon(), scaleUpFunction)
scaleDownButton := widget.NewButtonWithIcon("", theme.ContentRemoveIcon(), scaleDownFunction)
scaleButtonsGrid := container.NewGridWithColumns(1, scaleUpButton, scaleDownButton)
scaleButtonsBoxed := getContainerBoxed(scaleButtonsGrid)
return scaleButtonsBoxed
}
moveEmojiButtons := getMoveEmojiButtons()
scaleEmojiButtons := getScaleEmojiButtons()
moveAndScaleRow := container.NewHBox(layout.NewSpacer(), moveEmojiButtons, scaleEmojiButtons, layout.NewSpacer())
changeEmojiButton := getWidgetCentered(widget.NewButtonWithIcon("Change Emoji", theme.DocumentCreateIcon(), setChooseEmojiPageFunction))
submitButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm Changes", theme.ConfirmIcon(), func(){
nextPage(currentImage)
}))
page := container.NewVBox(title, backButton, widget.NewSeparator(), currentImageCentered, viewFullpageButton, widget.NewSeparator(), changeEmojiButton, submitButton, widget.NewSeparator(), moveAndScaleRow)
setPageContent(page, window)
}