-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathagg_dev.R
More file actions
673 lines (664 loc) · 19.7 KB
/
Copy pathagg_dev.R
File metadata and controls
673 lines (664 loc) · 19.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
#' Draw to a PPM file
#'
#' The PPM (Portable Pixel Map) format defines one of the simplest storage
#' formats available for
#' image data. It is basically a raw 8bit RGB stream with a few bytes of
#' information in the start. It goes without saying, that this file format is
#' horribly inefficient and should only be used if you want to play around with
#' a simple file format, or need a file-based image stream.
#'
#' @param filename The name of the file. Follows the same semantics as the file
#' naming in [grDevices::png()], meaning that you can provide a [sprintf()]
#' compliant string format to name multiple plots (such as the default value)
#' @param width,height The dimensions of the device
#' @param units The unit `width` and `height` is measured in, in either pixels
#' (`'px'`), inches (`'in'`), millimeters (`'mm'`), or centimeter (`'cm'`).
#' @param pointsize The default pointsize of the device in pt. This will in
#' general not have any effect on grid graphics (including ggplot2) as text
#' size is always set explicitly there.
#' @param background The background colour of the device
#' @param res The resolution of the device. This setting will govern how device
#' dimensions given in inches, centimeters, or millimeters will be converted
#' to pixels. Further, it will be used to scale text sizes and linewidths
#' @param scaling A scaling factor to apply to the rendered line width and text
#' size. Useful for getting the right dimensions at the resolution that you
#' need. If e.g. you need to render a plot at 4000x3000 pixels for it to fit
#' into a layout, but you find that the result appears to small, you can
#' increase the `scaling` argument to make everything appear bigger at the
#' same resolution.
#' @param snap_rect Should axis-aligned rectangles drawn with only fill snap to
#' the pixel grid. This will prevent anti-aliasing artifacts when two
#' rectangles are touching at their border.
#' @param bg Same as `background` for compatibility with old graphic device APIs
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.ppm')
#' agg_ppm(file)
#' plot(sin, -pi, 2*pi)
#' dev.off()
#'
agg_ppm <- function(
filename = 'Rplot%03d.ppm',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
file <- validate_path(filename)
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_ppm_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a PNG file
#'
#' The PNG (Portable Network Graphic) format is one of the most ubiquitous
#' today, due to its versatiliity
#' and widespread support. It supports transparency as well as both 8 and 16 bit
#' colour. The device uses default compression and filtering and will not use a
#' colour palette as this is less useful for antialiased data. This means that
#' it might be possible to compress the resulting image even more if size is of
#' concern (though the defaults are often very good). In contrast to
#' [grDevices::png()] the date and time will not be written to the file, meaning
#' that similar plot code will produce identical files (a good feature if used
#' with version control). It will, however, write in the dimensions of the image
#' based on the `res` argument.
#'
#' @inheritParams agg_ppm
#' @param bitsize Should the device record colour as 8 or 16bit
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.png')
#' agg_png(file)
#' plot(sin, -pi, 2*pi)
#' dev.off()
#'
agg_png <- function(
filename = 'Rplot%03d.png',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
bitsize = 8,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
file <- validate_path(filename)
if (!bitsize %in% c(8, 16)) {
stop('Only 8 and 16 bit is supported', call. = FALSE)
}
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_png_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.integer(bitsize),
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a TIFF file
#'
#' The TIFF (Tagged Image File Format) format is a very versatile raster image
#' storage format that supports 8 and 16bit colour mode, true transparency, as
#' well as a range of other features not relevant to drawing from R (e.g.
#' support for different colour spaces). The storage mode of the image data is
#' not fixed and different compression modes are possible, in contrast to PNGs
#' one-approach-fits-all. The default (uncompressed) will result in much larger
#' files than PNG, and in general PNG is a better format for many of the graphic
#' types produced in R. Still, TIFF has its purposes and sometimes this file
#' format is explicetly requested.
#'
#' @section Transparency:
#' TIFF have support for true transparency, meaning that the pixel colour is
#' stored in pre-multiplied form. This is in contrast to pixels being stored in
#' plain format, where the alpha values more function as a mask. The utility of
#' this is not always that important, but it is one of the benefits of TIFF over
#' PNG so it should be noted.
#'
#' @inheritParams agg_png
#' @param compression The compression type to use for the image data. The
#' standard options from the [grDevices::tiff()] function are available under
#' the same name.
#'
#' @note `'jpeg'` compression is only available if ragg is compiled with a
#' version of `libtiff` where jpeg support has been turned on.
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.tiff')
#' # Use jpeg compression
#' agg_tiff(file, compression = 'lzw+p')
#' plot(sin, -pi, 2*pi)
#' dev.off()
#'
agg_tiff <- function(
filename = 'Rplot%03d.tiff',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
compression = 'none',
bitsize = 8,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
file <- validate_path(filename)
encoding <- switch(compression, 'lzw+p' = , 'zip+p' = 1L, 0L)
compression <- switch(
compression,
'none' = 0L,
'rle' = 2L,
'lzw+p' = ,
'lzw' = 5L,
'jpeg' = 7L,
'zip+p' = ,
'zip' = 8L
)
if (!bitsize %in% c(8, 16)) {
stop('Only 8 and 16 bit is supported', call. = FALSE)
}
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_tiff_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.integer(bitsize),
compression,
encoding,
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a JPEG file
#'
#' The JPEG file format is a lossy compressed file format developed in
#' particular for digital photography. The format is not particularly
#' well-suited for line drawings and text of the type normally associated with
#' statistical plots as the compression algorithm creates noticable artefacts.
#' It is, however, great for saving image data, e.g. heightmaps etc. Thus, for
#' standard plots, it would be better to use [agg_png()], but for plots that
#' includes a high degree of raster image rendering this device will result in
#' smaller plots with very little quality degradation.
#'
#' @inheritParams agg_png
#' @param quality An integer between `0` and `100` defining the quality/size
#' tradeoff. Setting this to `100` will result in no compression.
#' @param smoothing A smoothing factor to apply before compression, from `0` (no
#' smoothing) to `100` (full smoothing). Can also by `FALSE` (no smoothing) or
#' `TRUE` (full smoothing).
#' @param method The compression algorithm to use. Either `'slow'`, `'fast'`, or
#' `'float'`. Default is `'slow'` which works best for most cases. `'fast'`
#' should only be used when quality is below `97` as it may result in worse
#' performance at high quality settings. `'float'` is a legacy options that
#' calculate the compression using floating point precission instead of with
#' integers. It offers no quality benefit and is often much slower.
#'
#' @note Smoothing is only applied if ragg has been compiled against a jpeg
#' library that supports smoothing.
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.jpeg')
#' agg_jpeg(file, quality = 50)
#' plot(sin, -pi, 2*pi)
#' dev.off()
#'
agg_jpeg <- function(
filename = 'Rplot%03d.jpeg',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
quality = 75,
smoothing = FALSE,
method = 'slow',
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
file <- validate_path(filename)
quality <- min(100, max(0, quality))
if (is.logical(smoothing)) smoothing <- if (smoothing) 100 else 0
smoothing <- min(100, max(0, smoothing))
method <- match.arg(tolower(method), c('slow', 'fast', 'float'))
method <- match(method, c('slow', 'fast', 'float')) - 1L
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_jpeg_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.integer(quality),
as.integer(smoothing),
method,
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a PNG file, modifying transparency on the fly
#'
#' The graphic engine in R only supports 8bit colours. This is for the most part
#' fine, as 8bit gives all the fidelity needed for most graphing needs. However,
#' this may become a limitation if you need to plot thousands of very
#' translucent shapes on top of each other. 8bit only afford a minimum of 1/255
#' alpha, which may end up accumulating to fully opaque at some point. This
#' device allows you to create a 16bit device that modifies the alpha level of
#' all incomming colours by a fixed multiplier, thus allowing for much more
#' translucent colours. The device will only modify transparent colour, so if
#' you pass in an opaque colour it will be left unchanged.
#'
#' @inheritParams agg_ppm
#' @param alpha_mod A numeric between 0 and 1 that will be multiplied to the
#' alpha channel of all transparent colours
#'
#' @export
#' @keywords internal
#'
agg_supertransparent <- function(
filename = 'Rplot%03d.png',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
alpha_mod = 1,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
file <- validate_path(filename)
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_supertransparent_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.double(alpha_mod),
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a buffer that can be accessed directly
#'
#' Usually the point of using a graphic device is to create a file or show the
#' graphic on the screen. A few times we need the image data for further
#' processing in R, and instead of writing it to a file and then reading it back
#' into R the `agg_capture()` device lets you get the image data directly from
#' the buffer. In contrast to the other devices this device returns a function,
#' that when called will return the current state of the buffer.
#'
#' @inheritParams agg_ppm
#'
#' @return A function that when called returns the current state of the buffer.
#' The return value of the function depends on the `native` argument. If `FALSE`
#' (default) the return value is a `matrix` of colour values and if `TRUE` the
#' return value is a `nativeRaster` object.
#'
#' @importFrom grDevices dev.list dev.off dev.cur dev.capture dev.set
#' @export
#'
#' @examples
#' cap <- agg_capture()
#' plot(1:10, 1:10)
#'
#' # Get the plot as a matrix
#' raster <- cap()
#'
#' # Get the plot as a nativeRaster
#' raster_n <- cap(native = TRUE)
#'
#' dev.off()
#'
#' # Look at the output
#' plot(as.raster(raster))
#'
agg_capture <- function(
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
name <- basename(tempfile('agg_capture_'))
.Call(
"agg_capture_c",
name,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
PACKAGE = 'ragg'
)
cap <- function(native = FALSE) {
current_dev = dev.cur()
if (names(current_dev)[1] == name) {
return(dev.capture(native = native))
}
all_dev <- dev.list()
if (!name %in% names(all_dev)) {
stop('The device (', name, ') is no longer open', call. = FALSE)
}
dev.set(all_dev[name])
on.exit(dev.set(current_dev))
dev.capture(native = native)
}
invisible(cap)
}
#' Capture drawing instructions without rendering
#'
#' While the point of a graphics device is usually to render the graphics, there
#' are a few situations where you are instead interested in only capturing the
#' instructions required to render the graphics. While all graphics devices can
#' be retrofitted for that using [dev.control()], they would still render to
#' their internal buffer even if you are only interested in the recorded
#' instructions, thus adding a performance penalty. `agg_record()` is a device
#' that does no rendering whatsoever, but has recording turned on by default
#' making it a no-overhead solution for plot recording.
#'
#' @inheritParams agg_ppm
#'
#' @export
#'
#' @examples
#' # Capture drawing instructions
#' agg_record()
#' plot(1:10, 1:10)
#' rec <- recordPlot()
#' dev.off()
#'
#' # Replay these on another device
#' file <- tempfile(fileext = '.png')
#' agg_png(file)
#' replayPlot(rec)
#' dev.off()
#'
agg_record <- function(
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
name <- basename(tempfile('agg_record_'))
.Call(
"agg_record_c",
name,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
PACKAGE = 'ragg'
)
invisible()
}
#' Draw to a WebP file
#'
#' The WebP format is a raster image format that provides improved lossless (and
#' lossy) compression for images on the web. Transparency is supported.
#'
#' @inheritParams agg_png
#' @param lossy Use lossy compression. Default is `FALSE`.
#' @param quality An integer between `0` and `100` defining either the quality
#' (if using lossy compression) or the compression effort (if using lossless).
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.webp')
#' agg_webp(file)
#' plot(sin, -pi, 2*pi)
#' dev.off()
#'
agg_webp <- function(
filename = 'Rplot%03d.webp',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
lossy = FALSE,
quality = 80,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
if (max(width, height) > 16383) {
stop('WebP does not support image width or height larger than 16383 px',
call. = FALSE)
}
if (quality < 0 || quality > 100) {
stop('quality must be between 0 and 100', call. = FALSE)
}
file <- validate_path(filename)
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_webp_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.logical(lossy),
as.integer(quality),
PACKAGE = 'ragg'
)
invisible()
}
#' Draw an animation to a WebP file
#'
#' The WebP format is a raster image format that provides improved lossless (and
#' lossy) compression for images on the web. Transparency is supported.
#'
#' @inheritParams agg_webp
#' @param filename The name of the file. This function does not perform page
#' number substitution as the other devices since it cannot produce multiple
#' pages.
#' @param delay Per-frame delay in milliseconds (single integer)
#' @param loop Number of loops (0 = infinite)
#'
#' @seealso [agg_webp()] for static WebP images
#'
#' @export
#'
#' @examples
#' file <- tempfile(fileext = '.webp')
#' agg_webp_anim(file, delay = 100, loop = 0)
#' for(i in 1:10) {
#' plot(sin(1:100 + i/10), type = 'l', ylim = c(-1, 1))
#' dev.flush()
#' }
#' dev.off()
agg_webp_anim <- function(
filename = 'Ranim.webp',
width = 480,
height = 480,
units = 'px',
pointsize = 12,
background = 'white',
res = 72,
scaling = 1,
snap_rect = TRUE,
lossy = FALSE,
quality = 80,
delay = 100L,
loop = 0L,
bg
) {
if (
environmentName(parent.env(parent.frame())) == "knitr" &&
deparse(sys.call(), nlines = 1, width.cutoff = 500) ==
'dev(filename = filename, width = dim[1], height = dim[2], ...)'
) {
units <- 'in'
}
if (max(width, height) > 16383) {
stop("WebP does not support image width or height larger than 16383 px",
call. = FALSE)
}
if (quality < 0 || quality > 100) {
stop('quality must be between 0 and 100', call. = FALSE)
}
if (delay < 0) {
stop('delay must be non-negative', call. = FALSE)
}
if (loop < 0) {
stop('loop count must be non-negative', call. = FALSE)
}
file <- validate_path(filename)
dim <- get_dims(width, height, units, res)
background <- if (missing(bg)) background else bg
.Call(
"agg_webp_anim_c",
file,
dim[1],
dim[2],
as.numeric(pointsize),
background,
as.numeric(res),
as.numeric(scaling),
as.logical(snap_rect),
as.logical(lossy),
as.integer(quality),
as.integer(delay),
as.integer(loop),
PACKAGE = "ragg"
)
invisible()
}