Skip to content

Instantly share code, notes, and snippets.

@smach
Last active March 12, 2025 13:46
Show Gist options
  • Save smach/b1669e05d5ead993bcb62839d4a738de to your computer and use it in GitHub Desktop.
Save smach/b1669e05d5ead993bcb62839d4a738de to your computer and use it in GitHub Desktop.
R Shiny app to turn JPGs and PNGs into .ico favicon files. Written mostly by GPT o3-mini-high with help from Claude and Shiny Assistant (and me)
options(shiny.maxRequestSize = 5 * 1024^2) # Limit uploads to 5 MB
library(shiny)
library(magick)
library(base64enc)
library(bslib)
# Helper function to sanitize file names
safeFileName <- function(filename) {
gsub("[^a-zA-Z0-9_.-]", "_", filename)
}
ui <- page_fluid(
theme = bs_theme(
version = 5,
bootswatch = "flatly",
primary = "#2C3E50"
),
tags$head(
tags$style("
.bg-white {
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%),
linear-gradient(-45deg, #ccc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #ccc 75%),
linear-gradient(-45deg, transparent 75%, #ccc 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
background-color: white;
}
")
),
# Header card
card(
full_screen = FALSE,
height = "auto",
card_header(
class = "bg-primary text-white",
h1("PNG/JPG to ICO Favicon Converter", class = "mb-0")
),
p(
class = "text-muted mt-2",
"Written mostly by ",
tags$a(
href = "https://openai.com/index/openai-o3-mini/",
target = "_blank",
"OpenAI o3-mini-high",
class = "text-decoration-none"
),
". Design suggested by ",
tags$a(
href = "https://gallery.shinyapps.io/assistant",
target = "_blank",
"Shiny Assistant",
class = "text-decoration-none"
),
". View code on GitHub gist: ", # Note the added space and comma here
tags$a(
href = "https://gist.github.com/smach/b1669e05d5ead993bcb62839d4a738de", # Replace with your actual gist URL
target = "_blank",
class = "text-decoration-none",
icon("github"), # This adds the GitHub icon
"" # Text that appears after the icon
)
)
),
layout_columns(
col_widths = c(4, 8),
gap = "1rem",
# Input controls card
card(
card_header("Settings"),
card_body(
fileInput("file", "Choose PNG or JPG File",
accept = c("image/png", "image/jpeg")),
checkboxGroupInput("sizes", "Select favicon sizes (pixels):",
choices = list("16x16" = 16, "32x32" = 32,
"48x48" = 48, "64x64" = 64),
selected = 32),
downloadButton("download", "Download Favicon File(s)",
class = "btn-primary btn-lg w-100")
)
),
# Preview card
card(
card_header("Previews"),
card_body(
uiOutput("img_previews")
)
)
)
)
server <- function(input, output, session) {
# Reactive expression to safely read the uploaded image
img_reactive <- reactive({
req(input$file)
if (!input$file$type %in% c("image/png", "image/jpeg")) {
stop("Uploaded file must be a PNG or JPG image.")
}
img <- tryCatch({
image_read(input$file$datapath)
}, error = function(e) {
stop("Could not read image. Please ensure the file is a valid image.")
})
img
})
# Reactive expression to generate preview images
preview_images <- reactive({
req(input$file, input$sizes)
sizes <- as.numeric(input$sizes)
raw_img <- img_reactive()
lapply(sizes, function(sz) {
img_scaled <- image_scale(raw_img, paste0(sz, "x", sz))
tmpfile <- tempfile(fileext = ".png")
image_write(img_scaled, path = tmpfile, format = "png")
uri <- dataURI(file = tmpfile, mime = "image/png")
list(size = sz, uri = uri)
})
})
# Dynamic UI for previews
output$img_previews <- renderUI({
req(preview_images())
images <- preview_images()
tagList(
div(
class = "d-flex flex-wrap gap-4 justify-content-center",
lapply(images, function(img_info) {
div(
class = "text-center preview-container",
style = "min-width: 100px;",
h4(paste0(img_info$size, " × ", img_info$size)),
div(
class = "transparency-grid border rounded p-3",
style = sprintf("width: %dpx; height: %dpx; display: flex; align-items: center; justify-content: center;",
img_info$size + 40, img_info$size + 40),
img(
src = img_info$uri,
width = img_info$size,
height = img_info$size,
style = "image-rendering: pixelated;"
)
)
)
})
)
)
})
# Download handler
output$download <- downloadHandler(
filename = function() {
req(input$file)
base <- safeFileName(tools::file_path_sans_ext(basename(input$file$name)))
sizes <- as.numeric(input$sizes)
if (length(sizes) == 1) {
paste0(base, "_", sizes, "x", sizes, ".ico")
} else {
paste0(base, "_favicons.zip")
}
},
content = function(file) {
req(input$file, input$sizes)
sizes <- as.numeric(input$sizes)
raw_img <- img_reactive()
if (length(sizes) == 1) {
sz <- sizes[1]
img_scaled <- image_scale(raw_img, paste0(sz, "x", sz))
image_write(img_scaled, path = file, format = "ico")
} else {
tmpDir <- tempdir()
ico_files <- sapply(sizes, function(sz) {
out_file <- file.path(tmpDir, paste0(
safeFileName(tools::file_path_sans_ext(basename(input$file$name))),
"_", sz, "x", sz, ".ico"
))
img_scaled <- image_scale(raw_img, paste0(sz, "x", sz))
image_write(img_scaled, path = out_file, format = "ico")
out_file
})
oldwd <- getwd()
setwd(tmpDir)
on.exit(setwd(oldwd), add = TRUE)
zip_file <- file.path(tmpDir, "favicons.zip")
utils::zip(zipfile = zip_file, files = basename(ico_files))
file.copy(zip_file, file)
}
}
)
}
shinyApp(ui, server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment