GithubHelp home page GithubHelp logo

matt-dray / pixeltrix Goto Github PK

View Code? Open in Web Editor NEW
20.0 1.0 0.0 439 KB

:space_invader::computer_mouse: R package: make pixel art interactively in a plot window, get a matrix, make a gif

License: Other

R 100.00%
pixel-art rstats rstats-package shiny sprites

pixeltrix's Introduction

pixeltrix

Project Status: Concept – Minimal or no implementation has been done yet, or the repository is only intended to be a limited example, demo, or proof-of-concept. R-CMD-check Codecov test coverage Blog posts

Create static and animated pixel art from an interactive plot window in R. Returns your image’s ‘blueprint’ as a matrix. Pixel + matrix = {pixeltrix}.

How to

You can install {pixeltrix} from GitHub. It has no dependencies unless you want to make gifs, which requires you to install {gifski}.

install.packages("remotes")  # if not yet installed
remotes::install_github("matt-dray/pixeltrix")
library(pixeltrix)

Basic use:

  1. Use click_pixels() to begin an interactive, clickable plot of squares (‘pixels’).
  2. Click individual pixels repetitively to cycle through their states.
  3. Press the Esc key when you’re done, or the ‘Finish’ button in RStudio’s plot window, to return a matrix that encodes your image.

You can also:

  • draw your matrix to the plotting window as an image with draw_pixels()
  • make changes by passing the matrix output from click_pixels() into edit_pixels()
  • create animation frames (a list of matrices) with frame_pixels() and write them to a gif with gif_pixels()

Limitations

This package does what I need it to do; it doesn’t match the quality of a real pixel art editor. Some known limitations are that:

  • you can only click one pixel at a time
  • each click only increments the pixel state by 1
  • you can’t change the number of pixel states on the fly, nor the colour palette

If your editor opens a separate graphics window (i.e. not RStudio), each click may result in a brief flash as the image refreshes, while a resized window may return to its original dimensions. You may also hear a bell sound on click, which you can disable by setting options(locatorBell = FALSE).

Examples

Static sprite

Let’s create a sprite of the player character from Pokémon (1996) using click_pixels():

pkmn_sprite <- click_pixels(
  n_rows   = 16,
  n_cols   = 14,
  n_states = 3,  # number of states that a pixel can take
  colours  = c("white", "#879afb", "grey20")  # Pokémon Blue palette
)
# Click squares in the plot window. Press <Esc> to end.

This opens an interactive plot window. You can click to cycle the pixels through each of the three states. Here’s how that looks in RStudio:

An RStudio window. The console has run the function click_pixels(blue) and has printed the message 'click squares in the plot window, press Esc to end.' In the plot pane is a 16 by 14 pixel grid with a sprite of the main character from the first generation of Pokemon games for the Game Boy. The background is white, the outlines are dark grey and the highlights are light blue There's a black grid around the pixels.

A matrix is returned when you’ve finished clicking and pressed Esc. Note that the values are zero-indexed and the corresponding colour palette is stored in a colours attribute.

str(pkmn_sprite)
#  int [1:16, 1:14] 0 0 0 0 0 0 2 2 0 0 ...
#  - attr(*, "colours")= Named chr [1:3] "white" "#879afb" "grey20"
#   ..- attr(*, "names")= chr [1:3] "0" "1" "2"

You can pass the matrix to edit_pixels() to reopen the interactive plotting window to make adjustments. You can also increase n_states or change the colour palette.

The draw_pixels() function simply plots your matrix, optionally with a new colour palette:

draw_pixels(
  m = pkmn_sprite,
  colours = c("#9bbc0f", "#8bac0f", "#306230")  # Game Boy palette
)

A 14 by 16 pixel grid with a sprite of the main character from the first generation of Pokemon games for the Game Boy. It's coloured using the green shades of the original Game Boy.

Animated sprite

You can create multiple animation frames with frame_pixels(). The prior frame is used as a template for the next.

Here’s how it might look to recreate Mario’s walk cycle from Super Mario Brothers (1983), including an interactive prompt to add more frames as required:

mario_frames <- frame_pixels(
  n_rows   = 16,
  n_cols   = 16,
  n_states = 4,
  colours  = c("#8861FE", "#F6B95B", "#EF151A", "#7F6D14")
)
# Click squares in the plot window. Press <Esc> to end.
# Add a frame? y/n: y
# Click squares in the plot window. Press <Esc> to end.
# Current frame count: 2
# Add a frame? y/n: y
# Click squares in the plot window. Press <Esc> to end.
# Current frame count: 3
# Add a frame? y/n: n
# Final frame count: 3

The structure of the object is a list of matrices, where each matrix is a frame of the animation.

str(mario_frames)
# List of 3
#  $ : int [1:16, 1:16] 0 0 0 0 0 0 0 0 1 1 ...
#   ..- attr(*, "colours")= Named chr [1:4] "#8861FE" "#F6B95B" "#EF151A" "#7F6D14"
#   .. ..- attr(*, "names")= chr [1:4] "0" "1" "2" "3"
#  $ : int [1:16, 1:16] 0 0 0 0 0 0 0 0 0 0 ...
#   ..- attr(*, "colours")= Named chr [1:4] "#8861FE" "#F6B95B" "#EF151A" "#7F6D14"
#   .. ..- attr(*, "names")= chr [1:4] "0" "1" "2" "3"
#  $ : int [1:16, 1:16] 0 0 0 0 0 0 0 0 0 0 ...
#   ..- attr(*, "colours")= Named chr [1:4] "#8861FE" "#F6B95B" "#EF151A" "#7F6D14"
#   .. ..- attr(*, "names")= chr [1:4] "0" "1" "2" "3"

You can then convert the frames to a gif with gif_pixels().

gif_pixels(
  frames  = mario_frames, 
  file    = "mario.gif",  # write location
  delay   = 0.15  # passed to gifski::save_gif()
)
# Inserting image 3 at 0.30s (100%)...
# Encoding to gif... done!
# [1] "mario.gif"

Which results in this:

An animated 16 by 16 pixel grid with a coloured sprite of Mario from the original Super Mario Bros for the NES. There are three frames that each show a step in Mario's walk cycle.

pixeltrix's People

Contributors

matt-dray avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

pixeltrix's Issues

Allow for simple animations

Allow for drawing frames of an animation, e.g. frame_pixels(). Perhaps the function starts with click_pixels(), stores the output matrix in a list, then reopens the plotting window with that matrix with edit_pixels(), and so on. Might have to pre-specify the number of frames, or ask the user if they want to continue.

The output list has an element per frame of the animation. You should be able to pass this to animate_pixels() to produce a little gif.

Update built-in dataset examples

Update the built-in datsets blue and mario given #3 and #17 (currently in branch 'md-colour'). The new versions need to carry the colours attribute that is now added by click_pixels().

Allow for more than binary pixel values

The simplest form is to provide 0 ('off') and 1 ('on') to the user in the output matrix.

Other states may be desirable. In pixel art, you may be afforded four colours to work with, so you'll need 0, 1, 2, 3 and 4.

If you use the package for something other than pixel art, multiple states could encode different meaning, rather than just colour.

I think the package will always work in the basis of clicking the plot window and won't get as complex as being able to input a value, e.g. on the command line. That means that each click of a pixel should cycle it through the available values, like 0, 1, 2, 0, 1, 2, etc. That might get painful if you have lots of potential values to cycle through. Could warn the user if they want lots of values (>5 or something)?

Regardless, for simplicity I think the output matrix should always be composed of consecutive integer values (starting from 0). It's then up to the user to convert this matrix as they see fit, e.g. mapping different hex colour values to each integer value in the matrix.

How do you test the interactive elements?

frame_pixels() executes readline(), which requires user input to continue. In a non-interactive environment, e.g. a test, I think the response is encoded as "" . The two responses possible in frame_pixels are yes and no, however, so how can I test for these responses?

click_pixels() uses locator(); how does this behave in a non-interactive test? The output from the function is a plot, so that could be tested against, at least. It would have to be a blank plot, since no user-clicks will be registered in the non-interactive environment.

Add a grid around the pixels

You can't see where each pixel's area is in a blank image(). This is especially bad when there's a large number of pixels. An overlaid grid, which can be subtle, will help. This could be an argument like grid = TRUE.

In other words, abline(). The h and v positions will have to be calculated given the n_row and n_col, as per the calculations that take place to match the x and y of a point click to the nearest pixel.

I implemented this sort of functionality in my pixel art blog post.

Grid isn't applied correctly with dimension 1

The grid is wrong when using a value of 1 for n_rows or n_cols and grid = TRUE.

click_pixels(n_rows = 2, n_cols = 1)

pixeltrix_2-1-grid-bug

The pixels are in the right place (two rows, one column), but the grid is not.

Dimension (`n_rows`, `n_cols`) of 1 results in error

Dimension of 1 provided to click_pixels() results in error.

click_pixels(n_rows = 1L)
#  Error in seq.default(0 - y_unit - (y_unit/2), 1 + y_unit + (y_unit/2), : 
# 'from' must be a finite number
click_pixels(n_cols = 1L)
#  Error in seq.default(0 - x_unit - (x_unit/2), 1 + x_unit + (x_unit/2), : 
# 'from' must be a finite number

Because Inf.

Consider the impact of the reversing/transposing of the matrix for the `image(), given an x or y dimension of 1.

Force abline() to c(-1, 1) if dimension is 1.

`n_states` not generated in `draw_pixels()` when matrix has no `colours` attribute

This occurs if you use a matrix in draw_pixels() without a colours attribute:

> matrix(1:9,3) |> draw_pixels()
# Error in get_greys(n_states) : object 'n_states' not found

I think n_states is derived from the colours attribute, but there isn't any derivation of n_states if the attribute is not there. So just need to infer n_states from the values in the input matrix.

This is already handled in edit_pixels(), so might need to copy from there.

Draw matrix to plot window

You should be able to pass an output matrix from click_pixels() or edit_pixels() to draw_pixels() (or similar) to print to the graphics window via image(). Allow colours to be provided for each value in the matrix.

Let user choose colours

By which I mean colours to pass to image() so the plot's pixels are shown with supplied colours.

This could mean users must supply either:

  1. A set of any colours whose length matches n_state
  2. A start and end colour, with interim colours being interpolated (e.g. the current default is a grey palette created with colorRampPalette())

Supplying a colour set for image() wouldn't result in those colours being provided in the output matrix (reasoning in #4). It's just simpler for the output to be coded as 0L:n_states.

Personally I'd like to use GameBoy colours for example (as per my RStudio theme, gamelad).

Let user select pixel state colours in `click_pixels()`

With click_pixels()you currently you get darkening shades of grey when you click a pixel consecutively.

Add a colours option (or similar name). Suggest greys are retained by default if user doesn't pass a vector of colours:

click_pixels(colours = NULL)

But a user could do this:

click_pixels(n_states = 4, n_colours = c("black", "red", "blue", "yellow))

Where length(colours) should match n_states. The order of colours is the order they will change when you click a single pixel consecutively. To begin, all pixels will take the first-provided colour. Might be annoying if you don't know that's about to happen.

Add a vignette

There's probably sufficient functions to warrant a vignette now. A good place to explain some design choices and limitations too.

`n_states` can be "lost" when using `frame_pixels()`

Will try and make this as reproducible as possible 😅

  1. start with
tmp <- frame_pixels(10L, 10L, n_states = 3L)
  1. click the top left pixel once.
  2. Esc.
  3. Add new frame.

Result = you lose a state (and thus colour will also change), i.e. n_states now equals 2.

If, however, you click the top left pixel twice in the first frame, the state is preserved.

Hope all this makes sense.

Session info
Session info ───────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.2.2 (2022-10-31)
 os       Fedora Linux 37 (Workstation Edition)
 system   x86_64, linux-gnu
 ui       RStudio
 language (EN)
 collate  en_GB.UTF-8
 ctype    en_GB.UTF-8
 tz       Europe/London
 date     2022-12-14
 rstudio  2022.07.2+576 Spotted Wakerobin (desktop)
 pandoc   2.14.0.3 @ /usr/libexec/rstudio/bin/pandoc/ (via rmarkdown)

─ Packages ───────────────────────────────────────────────────────────────────────────────────────
 package     * version    date (UTC) lib source
 assertthat    0.2.1      2019-03-21 [2] CRAN (R 4.2.1)
 cachem        1.0.6      2021-08-19 [2] CRAN (R 4.2.1)
 callr         3.7.3      2022-11-02 [2] CRAN (R 4.2.1)
 cli           3.4.1      2022-09-23 [2] CRAN (R 4.2.1)
 colorspace    2.0-3      2022-02-21 [2] CRAN (R 4.2.1)
 CoprManager   0.4.0      2022-11-24 [4] local
 countrycode   1.4.0      2022-05-04 [2] CRAN (R 4.2.1)
 crayon        1.5.2      2022-09-29 [2] CRAN (R 4.2.1)
 curl          4.3.3      2022-10-06 [2] CRAN (R 4.2.1)
 data.table    1.14.6     2022-11-16 [2] CRAN (R 4.2.2)
 DBI           1.1.3      2022-06-18 [2] CRAN (R 4.2.1)
 devtools    * 2.4.5      2022-10-11 [2] CRAN (R 4.2.1)
 digest        0.6.30     2022-10-18 [2] CRAN (R 4.2.1)
 dotCall64     1.0-2      2022-10-03 [2] CRAN (R 4.2.1)
 dplyr         1.0.10     2022-09-01 [2] CRAN (R 4.2.1)
 ellipsis      0.3.2      2021-04-29 [2] CRAN (R 4.2.1)
 evaluate      0.18       2022-11-07 [2] CRAN (R 4.2.1)
 fansi         1.0.3      2022-03-24 [2] CRAN (R 4.2.1)
 fastmap       1.1.0      2021-01-25 [2] CRAN (R 4.2.1)
 fields        14.1       2022-08-12 [2] CRAN (R 4.2.1)
 fs            1.5.2      2021-12-08 [2] CRAN (R 4.2.1)
 generics      0.1.3      2022-07-05 [2] CRAN (R 4.2.1)
 ggplot2       3.4.0      2022-11-04 [2] CRAN (R 4.2.1)
 glue          1.6.2      2022-02-24 [2] CRAN (R 4.2.1)
 gridExtra     2.3        2017-09-09 [2] CRAN (R 4.2.1)
 gtable        0.3.1      2022-09-01 [2] CRAN (R 4.2.1)
 htmltools     0.5.4      2022-12-07 [2] CRAN (R 4.2.2)
 htmlwidgets   1.5.4      2021-09-08 [2] CRAN (R 4.2.1)
 httpuv        1.6.6      2022-09-08 [2] CRAN (R 4.2.1)
 httr          1.4.4      2022-08-17 [2] CRAN (R 4.2.1)
 jsonlite      1.8.4      2022-12-06 [2] CRAN (R 4.2.2)
 knitr         1.41       2022-11-18 [2] CRAN (R 4.2.2)
 later         1.3.0      2021-08-18 [2] CRAN (R 4.2.1)
 lifecycle     1.0.3      2022-10-07 [2] CRAN (R 4.2.1)
 lubridate     1.9.0      2022-11-06 [2] CRAN (R 4.2.1)
 magrittr      2.0.3      2022-03-30 [2] CRAN (R 4.2.1)
 maps          3.4.1      2022-10-30 [2] CRAN (R 4.2.1)
 memoise       2.0.1      2021-11-26 [2] CRAN (R 4.2.1)
 mime          0.12       2021-09-28 [2] CRAN (R 4.2.1)
 miniUI        0.1.1.1    2018-05-18 [2] CRAN (R 4.2.1)
 munsell       0.5.0      2018-06-12 [2] CRAN (R 4.2.1)
 oai           0.4.0      2022-11-10 [2] CRAN (R 4.2.1)
 pillar        1.8.1      2022-08-19 [2] CRAN (R 4.2.1)
 pixeltrix   * 0.1.1      2022-12-13 [1] Github (matt-dray/pixeltrix@d454979)
 pkgbuild      1.4.0      2022-11-27 [2] CRAN (R 4.2.2)
 pkgconfig     2.0.3      2019-09-22 [2] CRAN (R 4.2.1)
 pkgload       1.3.2      2022-11-16 [2] CRAN (R 4.2.2)
 plyr          1.8.8      2022-11-11 [2] CRAN (R 4.2.2)
 prettyunits   1.1.1      2020-01-24 [2] CRAN (R 4.2.1)
 processx      3.8.0      2022-10-26 [2] CRAN (R 4.2.1)
 profvis       0.3.7      2020-11-02 [2] CRAN (R 4.2.1)
 promises      1.2.0.1    2021-02-11 [2] CRAN (R 4.2.1)
 ps            1.7.2      2022-10-26 [2] CRAN (R 4.2.1)
 purrr         0.3.5      2022-10-06 [2] CRAN (R 4.2.1)
 R6            2.5.1      2021-08-19 [2] CRAN (R 4.2.1)
 Rcpp          1.0.9      2022-07-08 [2] CRAN (R 4.2.1)
 remotes       2.4.2      2021-11-30 [2] CRAN (R 4.2.1)
 rlang         1.0.6      2022-09-24 [2] CRAN (R 4.2.1)
 rmarkdown     2.18       2022-11-09 [2] CRAN (R 4.2.1)
 rstudioapi    0.14       2022-08-22 [2] CRAN (R 4.2.1)
 scales        1.2.1      2022-08-20 [2] CRAN (R 4.2.1)
 sessioninfo   1.2.2      2021-12-06 [2] CRAN (R 4.2.1)
 shiny         1.7.3      2022-10-25 [2] CRAN (R 4.2.1)
 socialmixr    0.2.0      2022-10-27 [2] CRAN (R 4.2.1)
 spam          2.9-1      2022-08-07 [2] CRAN (R 4.2.1)
 stringi       1.7.8      2022-07-11 [2] CRAN (R 4.2.1)
 stringr       1.5.0      2022-12-02 [2] CRAN (R 4.2.2)
 tibble        3.1.8      2022-07-22 [2] CRAN (R 4.2.1)
 tidyselect    1.2.0      2022-10-10 [2] CRAN (R 4.2.1)
 timechange    0.1.1      2022-11-04 [2] CRAN (R 4.2.1)
 urlchecker    1.0.1      2021-11-30 [2] CRAN (R 4.2.1)
 usethis     * 2.1.6      2022-05-25 [2] CRAN (R 4.2.1)
 utf8          1.2.2      2021-07-24 [2] CRAN (R 4.2.1)
 vctrs         0.5.1      2022-11-16 [2] CRAN (R 4.2.2)
 viridis       0.6.2      2021-10-13 [2] CRAN (R 4.2.1)
 viridisLite   0.4.1      2022-08-22 [2] CRAN (R 4.2.1)
 wpp2017       1.2-3      2020-02-10 [2] CRAN (R 4.2.1)
 xfun          0.35       2022-11-16 [2] CRAN (R 4.2.2)
 xml2          1.3.3      2021-11-30 [2] CRAN (R 4.2.1)
 xtable        1.8-4      2019-04-21 [2] CRAN (R 4.2.1)
 yaml          2.3.6      2022-10-18 [2] CRAN (R 4.2.1)
 ympes       * 0.2.1.9000 2022-12-08 [1] local

 [1] /home/tim/R/x86_64-redhat-linux-gnu-library/4.2
 [2] /usr/local/lib/R/library
 [3] /usr/lib64/R/library
 [4] /usr/share/R/library

──────────────────────────────────────────────────────────────────────────────────────────────────

Allow user to change grid colour

The grid that overlays the interactive plot window is black. That's not very useful when the pixelset is black or dark. Provide and argument that lets the user choose the grid colour (or possibly autoselect black or white depending on the pixel colours selected, although which would you choose for the grid if the user is selecting black and white pixels?)

User should be able to provide a matrix of their own for editing

Maybe a m(atrix) argument overrides supply of n_row and n_col?

But additional logic is required when editing a provided matrix. Need to assess things like its dimensions and how many values it takes (could just start by insisting 0,1 only, but ideally will allow for more in future as per #4).

So maybe it's a separate 'edit' function, like edit_pixels() rather than click_pixels()?

`edit_pixels()` not allowing for more `n_states`

I wanted to edit y, which has three states, by adding a fourth state.

edit_pixels(y, n_states = 4, c("grey10", "lightgreen", "darkgreen", "white"))
# Error: Length of argument 'colours' should match the number of unique pixel states (3).

The length of the colours argument should be equal-to or more than n_states in y; in fact, I've specified it should now be 4 in the n_states argument.

How do you prevent the plot window's size from resetting?

In RStudio you can adjust the plot pane dimensions and any new plots will be produced at that size.

When running from the terminal, the dimensions are reset to default for each new plot. (Tested only on my own macOS machine in Terminal.)

That's a problem in {pixeltrix} because a new plot is rendered after you click a pixel, so after every click the plot window will resize to default.

Is there a way to 'remember' the dimensions a user selects and render new plots to a window of that size?

This isn't a significant problem in my own use of the package.

Output an image file

Write to SVG or PNG. The package already saves a gif output for animation. Why not static images? (Or maybe it does somehow and I forgot.) At the moment you would have to draw_pixels() and then use the 'Export' functionality if you're using RStudio.

Let user select gif 'engine'

Could allow for the user's engine of choice when making gifs with gif_pixels(), e.g. {gifski} (currently implemented) or {magick} or something else (what?). So like gif_pixels(engine = "gifski"). Make it the user's responsibility to install these gif-making packages separately. {magick} would likely be a heavy dependency just to make a gif, so I don't think we want it in the DESCRIPTION here. Also I think {gifski} has some Rust-related requirements?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.