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) }