Final set of original 2D and 3D mosaic functions added to pkg.

This commit is contained in:
Ryan Timpe
2019-03-08 16:26:22 -05:00
parent 80351b418a
commit 5fef7617f4
8 changed files with 324 additions and 0 deletions
+15
View File
@@ -15,3 +15,18 @@ WarholBea.png
logomovie.mp4
logoweb.mp4
sets
1_CreateLogo.R
1_CreateSet.R
1_CreateSet_Penguin.R
1_CreateSet_Rocket.R
1_CreateSet_Trex.R
housemovie.mp4
houseweb.mp4
penguinmovie.mp4
penguinweb.mp4
rocketmovie.mp4
rocketmovie2.mp4
rocketweb.mp4
rocketweb2.mp4
trexmovie.mp4
trexweb.mp4
+4
View File
@@ -1,11 +1,15 @@
# Generated by roxygen2: do not edit by hand
export("%>%")
export(collect_3d)
export(collect_bricks)
export(convert_to_match_color)
export(display_3d)
export(display_pieces)
export(display_set)
export(generate_instructions)
export(image_to_bricks)
export(legoize)
export(scale_image)
export(table_pieces)
importFrom(magrittr,"%>%")
+131
View File
@@ -0,0 +1,131 @@
#' Convert image output from scale_image() to bricks
#'
#' @param image_list List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.
#' @param mosaic_height Maximum height of 3D mosiacs in LEGO plates. 3 plates = 1 brick. This is also the maximum # of distinct elevations.
#' @param highest_el Brick height is determined by brightness of color. Use \code{highest_el = 'dark'} for darkest bricks to have \code{mosaic_height}.
#' @return A list with elements \code{threed_elevation} and \code{threed_hillshade} to created 3D mosiacs with the \code{rayshader} package.
#' @export
#'
collect_3d <- function(image_list, mosaic_height = 6, highest_el = "light"){
#Get previous data
in_list <- image_list
if(in_list$mosaic_type != "flat")stop("3D mosaics can only be generated with 'flat' mosaics. Set this input in the 'collect_bricks' function.")
BrickIDs <- in_list$ID_bricks
img_lego <- in_list$Img_lego
#Number of 'pixels' on a side of a single-stud brick. I think this should be fixed for now
ex_size <- 15
lego_expand <- img_lego %>%
dplyr::select(x, y, Lego_name, Lego_color) %>%
dplyr::mutate(stud_id = dplyr::row_number())
lego_expand2 <- expand.grid(x = (min(lego_expand$x)*ex_size):(max(lego_expand$x+1)*ex_size),
y = (min(lego_expand$y)*ex_size):(max(lego_expand$y+1)*ex_size)) %>%
dplyr::mutate(x_comp = x %/% ex_size,
y_comp = y %/% ex_size) %>%
dplyr::left_join(lego_expand %>% dplyr::rename(x_comp = x, y_comp = y),
by = c("x_comp", "y_comp")) %>%
dplyr::left_join(BrickIDs %>% dplyr::select(brick_id, x_comp = x, y_comp = y),
by = c("x_comp", "y_comp")) %>%
dplyr::select(-x_comp, -y_comp) %>%
dplyr::left_join(lego_colors %>% dplyr::select(Lego_name = Color, R_lego, G_lego, B_lego),
by = "Lego_name") %>%
dplyr::do(
if(highest_el == "dark"){
dplyr::mutate(., elevation = (1-((R_lego + G_lego + B_lego )/3)) * 1000)
} else {
dplyr::mutate(., elevation = ( ((R_lego + G_lego + B_lego )/3)) * 1000)
}
) %>%
#Round elevation to nearest 1/height
dplyr::mutate(elevation = as.numeric(as.factor(cut(elevation, mosaic_height)))) %>%
dplyr::mutate(y = max(y)-y) %>%
dplyr::filter(!is.na(elevation)) %>%
#Calculate stud placement... radius of 1/3 and height of 0.5 plate
dplyr::group_by(stud_id) %>%
dplyr::mutate(x_mid = median(x), y_mid = median(y),
stud = ((x-x_mid)^2 + (y-y_mid)^2)^(1/2) < ex_size/3) %>%
dplyr::ungroup() %>%
dplyr::mutate(elevation = ifelse(stud, elevation+0.5, elevation)) %>%
dplyr::mutate_at(dplyr::vars(R_lego, G_lego, B_lego), dplyr::funs(ifelse(stud, .-0.1, .))) %>%
dplyr::mutate_at(dplyr::vars(R_lego, G_lego, B_lego), dplyr::funs(ifelse(. < 0, 0, .)))
edges <- dplyr::bind_rows(list(
lego_expand2 %>% dplyr::filter(x == min(x)) %>% dplyr::mutate(x = x-1),
lego_expand2 %>% dplyr::filter(x == max(x)) %>% dplyr::mutate(x = x+1),
lego_expand2 %>% dplyr::filter(y == min(y)) %>% dplyr::mutate(y = y-1),
lego_expand2 %>% dplyr::filter(y == max(y)) %>% dplyr::mutate(y = y+1)
)) %>%
dplyr:: mutate(R_lego = 1, G_lego = 1, B_lego = 1,
elevation = 0,
brick_id = NA)
#Elevation Matrix
lego_elmat <- lego_expand2 %>%
dplyr::bind_rows(edges) %>%
dplyr::select(x, y, elevation) %>%
tidyr::spread(y, elevation) %>%
dplyr::select(-x) %>%
as.matrix()
#Hillshade matrix
lego_hillshade_m <- array(dim = c(length(unique(lego_expand2$y)),
length(unique(lego_expand2$x)),
3))
lego_expand_color <- lego_expand2 %>%
dplyr::group_by(brick_id) %>%
#This darkens the edge of each brick, to look like they are separated
dplyr::mutate_at(dplyr::vars(R_lego, G_lego, B_lego),
dplyr::funs(ifelse((x == min(x) | y == min(y) | x == max(x) | y == max(y)), .*0.75, .))) %>%
dplyr::ungroup()
lego_hillshade_m[,,1] <- lego_expand_color %>%
dplyr::select(x, y, R_lego) %>%
tidyr::spread(x, R_lego) %>%
dplyr::select(-y) %>%
as.matrix()
lego_hillshade_m[,,2] <- lego_expand_color %>%
dplyr::select(x, y, G_lego) %>%
tidyr::spread(x, G_lego) %>%
dplyr::select(-y) %>%
as.matrix()
lego_hillshade_m[,,3] <- lego_expand_color %>%
dplyr::select(x, y, B_lego) %>%
tidyr::spread(x, B_lego) %>%
dplyr::select(-y) %>%
as.matrix()
#Return
in_list[["threed_elevation"]] <- lego_elmat
in_list[["threed_hillshade"]] <- lego_hillshade_m
return(in_list)
}
#' brickr wrapper for rayshader::plot_3d() to display 3D mosaics. Requires rayshader.
#'
#' @param image_list List output from collect_3d(). Contains element \code{threed_elevation} and \code{threed_hillshade}.
#' @param solidcolor Hex color of mosaic base. Only renders on bottom.
#' @param ... All other inputs from rayshader::plot_3d() EXCEPT \code{hillshade}, \code{soliddepth}, and \code{zscale}.
#' @return 3D mosaic rendered in the 'rgl' package.
#' @export
display_3d <- function(image_list, solidcolor = "#a3a2a4", ...){
#Requires Rayshader
if (!requireNamespace("rayshader", quietly = TRUE)) {
stop("Package \"rayshader\" needed for this function to work. Please install it.",
call. = FALSE)
}
image_list$`threed_hillshade`%>%
rayshader::plot_3d(image_list$`threed_elevation`, zscale=0.125,
solidcolor=solidcolor, ...)
}
+98
View File
@@ -0,0 +1,98 @@
#' Generate required bricks as a data frame.
#'
#' @param image_list List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.
#' @return Data frame of piece counts by LEGO color name and size.
#' @export
#'
table_pieces <- function(image_list){
pcs <- image_list$pieces
pcs %>%
dplyr::select(-Lego_color) %>%
tidyr::spread(Brick_size, n, fill = 0) %>%
dplyr::rename(`LEGO Brick Color` = Lego_name)
}
#' Graphically display required bricks.
#'
#' @param image_list List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.
#' @return Plot object of required bricks by color and size.
#' @export
#'
display_pieces <- function(image_list){
in_list <- image_list
pcs <- in_list$pieces
if(in_list$mosaic_type == "flat"){
pcs_coords <- dplyr::tibble(
Brick_size = c("1 x 1", "2 x 1", "3 x 1", "4 x 1", "2 x 2", "4 x 2"),
xmin = c(0, 0, 0, 0, 6, 6),
xmax = c(1, 2, 3, 4, 8, 8),
ymin = c(0, 2, 4, 6, 0, 3),
ymax = c(1, 3, 5, 7, 2, 7)
)
} else {
pcs_coords <- dplyr::tibble(
Brick_size = c("1 x 2", "2 x 2", "3 x 2", "4 x 2"),
xmin = c(0, 5, 5, 0),
xmax = c(2, 7, 7, 2),
ymin = c(0, 0, 3, 2),
ymax = c(1, 2, 6, 6)
)
}
#This function creates nodes in each brick for stud placement
pcs_coords <- pcs_coords %>%
dplyr::mutate(studs = purrr::pmap(list(xmin, xmax, ymin, ymax), function(a, b, c, d){
expand.grid(x=seq(a+0.5, b-0.5, by=1),
y=seq(c+0.5, d-0.5, by=1))
}))
pcs2 <- pcs %>%
dplyr::arrange(Lego_color) %>%
dplyr::mutate(Lego_name = factor(Lego_name,
levels = c("Black",
unique(Lego_name)[!(unique(Lego_name) %in% c("Black", "White"))],
"White"))) %>%
dplyr::left_join(pcs_coords, by = "Brick_size")
if(in_list$mosaic_type == "flat"){
coord_xlim <- c(-0.5, 10)
facet_cols <- 5
} else {
coord_xlim <- c(-0.5, 9)
facet_cols <- 6
}
pcs2 %>%
ggplot2::ggplot() +
ggplot2::geom_rect(ggplot2::aes(xmin=xmin, xmax=xmax, ymin=-ymin, ymax=-ymax,
fill = Lego_color), color = "#333333")+
ggplot2::scale_fill_identity() +
ggplot2::geom_point(data = pcs2 %>% tidyr::unnest(studs),
ggplot2::aes(x=x, y=-y),
color = "#cccccc", alpha = 0.25,
shape = 1, size = 2) +
ggplot2::geom_text(
ggplot2::aes(x = xmax + 0.25, y = -(ymin+ymax)/2, label = paste0("x", n)),
hjust = 0, vjust = 0.5, size = 3.5) +
ggplot2::coord_fixed(xlim = coord_xlim) +
ggplot2::labs(title = (if(in_list$mosaic_type == "stacked"){
"Suggested LEGO Bricks"
}else{"Suggested LEGO Plates"}),
caption = (if(in_list$mosaic_type == "stacked"){
"Mosaic is 2-bricks deep. Can substitute 2-stud bricks for 1-stud alternatives for a thinner mosaic."}else{""})
) +
ggplot2::facet_wrap(~Lego_name, ncol=facet_cols) +
ggplot2::theme_minimal() +
ggplot2::theme( panel.background = ggplot2::element_rect(fill = "#7EC0EE"),
strip.background = ggplot2::element_rect(fill = "#F7F18D"),
strip.text = ggplot2::element_text(color = "#333333", face = "bold"),
axis.line = ggplot2::element_blank(),
axis.title.x = ggplot2::element_blank(),
axis.text.x = ggplot2::element_blank(),
axis.title.y = ggplot2::element_blank(),
axis.text.y = ggplot2::element_blank(),
panel.grid = ggplot2::element_blank())
}
+21
View File
@@ -0,0 +1,21 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/image_to_3d.R
\name{collect_3d}
\alias{collect_3d}
\title{Convert image output from scale_image() to bricks}
\usage{
collect_3d(image_list, mosaic_height = 6, highest_el = "light")
}
\arguments{
\item{image_list}{List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.}
\item{mosaic_height}{Maximum height of 3D mosiacs in LEGO plates. 3 plates = 1 brick. This is also the maximum # of distinct elevations.}
\item{highest_el}{Brick height is determined by brightness of color. Use \code{highest_el = 'dark'} for darkest bricks to have \code{mosaic_height}.}
}
\value{
A list with elements \code{threed_elevation} and \code{threed_hillshade} to created 3D mosiacs with the \code{rayshader} package.
}
\description{
Convert image output from scale_image() to bricks
}
+21
View File
@@ -0,0 +1,21 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/image_to_3d.R
\name{display_3d}
\alias{display_3d}
\title{brickr wrapper for rayshader::plot_3d() to display 3D mosaics. Requires rayshader.}
\usage{
display_3d(image_list, solidcolor = "#a3a2a4", ...)
}
\arguments{
\item{image_list}{List output from collect_3d(). Contains element \code{threed_elevation} and \code{threed_hillshade}.}
\item{solidcolor}{Hex color of mosaic base. Only renders on bottom.}
\item{...}{All other inputs from rayshader::plot_3d() EXCEPT \code{hillshade}, \code{soliddepth}, and \code{zscale}.}
}
\value{
3D mosaic rendered in the 'rgl' package.
}
\description{
brickr wrapper for rayshader::plot_3d() to display 3D mosaics. Requires rayshader.
}
+17
View File
@@ -0,0 +1,17 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/piece_count.R
\name{display_pieces}
\alias{display_pieces}
\title{Graphically display required bricks.}
\usage{
display_pieces(image_list)
}
\arguments{
\item{image_list}{List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.}
}
\value{
Plot object of required bricks by color and size.
}
\description{
Graphically display required bricks.
}
+17
View File
@@ -0,0 +1,17 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/piece_count.R
\name{table_pieces}
\alias{table_pieces}
\title{Generate required bricks as a data frame.}
\usage{
table_pieces(image_list)
}
\arguments{
\item{image_list}{List output from collect_bricks() or image_to_bricks(). Contains an element \code{Img_lego}.}
}
\value{
Data frame of piece counts by LEGO color name and size.
}
\description{
Generate required bricks as a data frame.
}