2

I'd like to add simple cropping feature to my shiny app, where uploaded images can be cropped and saved to the file system.

The first part of uploading an image and rendering with croppie.js works, but I cannot get the second part of cropping and saving cropped image to www/ folder on button click to work.

The first problem is that the JS variable basic can not be referenced anymore in the second JS Code block. And even if, I do not know how I could use the variable croppedImage then to save the image as a jpeg file to the www folder.

EDIT:

Ok, I found a way to pass the base64 encoded image from Javascript to R with Shiny.onInputChange. Now I need to decode the image and save with R. This does not work though, an image file is saved, but it cannot be opened.

library(shiny)
library(shinyjs)
library(stringr)
library(base64enc)

ui <- fluidPage(
  shinyjs::useShinyjs(),
  tags$head(HTML('<link href="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.4.0/croppie.css" rel="stylesheet">')),
  tags$script(src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"),
  tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/croppie/2.4.0/croppie.js"),
  fileInput("image_upload",
            label = "Image", width = "300px",
            accept = c("image/png", "image/jpeg", "image/jpg")),
  div(id = "demo-basic", style = "
    width: 900px;
    height: 600px;"),
  br(),
  br(),
  actionButton("crop", "Crop image")
)

server <- function(input, output, session) {

  observeEvent(input$image_upload, {
    file.rename(input$image_upload$datapath, str_c("www/image0.jpg"))

    runjs(paste0("
                $(function () {
                  var basic = $('#demo-basic').croppie({
                viewport: {
                  width: 900,
                  height: 600
                }
        });
          basic.croppie('bind', {
            url: 'image0.jpg'
          });
    });
    "))
  })

 observeEvent(input$crop, {
    runjs("
var basic = $('#demo-basic').croppie({
  viewport: {
    width: 900,
    height: 600
  }
});

basic.croppie('result', {
  format: 'jpeg'
}).then(function(croppedImage) {
  Shiny.onInputChange('cropped', croppedImage);
});
    ")
  })

  observeEvent(input$cropped, {
    # This does not work yet
    enc <- input$cropped
    outconn <- file("cropped.jpeg","wb")
    base64decode(what = enc, output = outconn)
    close(outconn)
  })
}

shinyApp(ui, server)
needRhelp
  • 2,948
  • 2
  • 24
  • 48

1 Answers1

0

I know this is a super-late answer, but someone might find this useful, so sharing what was the problem with the above:

  • croppie adds the content type at the beginning of the base64-encoded text, and that needs to be removed before decoding
  • init croppie once (at app start)
  • wait with bind until everything is visible

I added a few extra small tweaks for (IMO) better reproducibility, readability, and some other features (e.g. rotation), and update the JS lib to most recent version on CDN:

library(shiny)
library(shinyjs)
library(stringr)
library(base64enc)

ui <- fluidPage(
  shinyjs::useShinyjs(),
  tags$head(HTML('<link href="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.css" rel="stylesheet">')),
  tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.js"),
  fileInput("image_upload", label = "Upload image", accept = c("image/png", "image/jpeg", "image/jpg")),
  div(id = "croppier", style = "width: 300px; height: 600px;"),
  br(),
  br(),
  actionButton("rotate_left", "Rotate left"),
  actionButton("save", "Save image"),
  actionButton("rotate_right", "Rotate right")
)

server <- function(input, output, session) {
  
  runjs("$('#croppier').croppie({
          viewport: {
            width: 280,
            height: 580
          },
          showZoomer: false,
          enableResize: true,
          enableOrientation: true,
          enforceBoundary: false,
          mouseWheelZoom: 'ctrl'
        });")
  
  observeEvent(input$image_upload, {
    file.copy(input$image_upload$datapath, str_c("www/croppie.jpg"), overwrite = TRUE)
    runjs("$('#croppier').croppie('bind', {url: 'croppie.jpg'});")
  })
 
  observeEvent(input$rotate_left, {
    runjs("$('#croppier').croppie('rotate', 90);")
  })
  observeEvent(input$rotate_right, {
    runjs("$('#croppier').croppie('rotate', -90);")
  })
   
  observeEvent(input$save, {
    runjs("$('#croppier').croppie('result', {
            format: 'jpeg', type : 'base64'
          }).then(function(img) {
            Shiny.setInputValue('cropped', img);
          });")
  })
  
  observeEvent(input$cropped, {
    ## strip data:img part from the base64 string
    enc <- sub('^data:image\\/jpeg;base64,', '', input$cropped)
    outconn <- file("cropped.jpeg","wb")
    base64decode(what = enc, output = outconn)
    close(outconn)
  })
}

shinyApp(ui, server)
daroczig
  • 28,004
  • 7
  • 90
  • 124