Making Old CRT-style Television Screens with Racket and `images/flomap`

So I was messing around with Racket’s new excellent flomap module, and I came up with a script that turns images into televised versions of themselves.

The results of this look like:

A CRT cat

You’ll need Racket 5.3 to use this. Call it like this:

(televise (make-object bitmap% "some-filename.png"))

and you’ll get a bitmap% that you can then save to a file or display in DrRacket.

#lang racket

(require images/flomap
         racket/draw
         images/icons/style)

;; Turns 'bitmap' into an old CRT television-like version of itself.
(define (televise bitmap
                  #:color-offset [color-offset 2]
                  #:lens-bend [lens-bend (* 2/3 pi)])
  (define imgfm (fmsqr (bitmap->flomap bitmap)))
  ;; fake gamma control

  (define noise-offset-big
    ;; Approximating a square wave with power series (you can tell i
    ;; was in signals and systems class today)
    ;; This creates the "large" bands on the screen.
    (flomap-normalize
     (build-flomap 1 (flomap-width imgfm) (flomap-height imgfm)
                   (λ(k x y-not)
                     (define y (+ 12 (/ y-not 20)))
                     (+ (* 1/1 (sin (* 1 y)))
                        (* 1/3 (sin (* 3 y)))
                        (* 1/5 (sin (* 5 y)))
                        (* 1/7 (sin (* 7 y)))
                        (* 1/9 (sin (* 9 y))))))))

  (define noise-offset
    ;; Smaller noise bands
    (flomap-normalize
     (build-flomap 1 (flomap-width imgfm) (flomap-height imgfm)
                   (λ(k x y)
                     (sin y)))))

  (define (flomap-move fm dx dy)
    ;; Offset a flomap.
    (flomap-inset fm dx dy (- dx) (- dy)))

  ;; Offset each channel of the RGB picture to make it look worse
  (match-define (list imgfmr imgfmg imgfmb)
                (map (curry flomap-ref-component imgfm) '(1 2 3)))
  (define flomap-rgbshifted
    (flomap-append-components (flomap-move imgfmr color-offset color-offset)
                              (flomap-move imgfmg (- color-offset) (- color-offset))
                              (flomap-move imgfmb (- color-offset) color-offset)))

  (define screen
    ;; Build the actual CRT screen, without shading
    (flomap-transform
     ;; Copy alpha channel from original:
     (flomap-append-components (flomap-ref-component imgfm 0)
                               (fm+  (fm* noise-offset     0.1)
                                (fm+ (fm* noise-offset-big 0.1)
                                     flomap-rgbshifted)))
     ;; Fisheye:
     (flomap-projection-transform
      (equal-area-projection lens-bend)
      (stereographic-projection lens-bend)
      #f)))

  ;; Finally, give glassy gloss to the TV screen.
  (bitmap-render-icon (flomap->bitmap screen)
                      5/8 glass-icon-material))