/
index.html
228 lines (226 loc) · 13.5 KB
/
index.html
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
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>PureImage: raster image processing for Scala</title>
<meta content='black-translucent' name='apple-mobile-web-app-status-bar-style'>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' name='viewport'>
<link href='css/reveal.min.css' rel='stylesheet'>
<link href='css/theme/default.css' id='theme' rel='stylesheet'>
<!-- For syntax highlighting -->
<link href='lib/css/zenburn.css' rel='stylesheet'>
<!-- If the query includes 'print-pdf', use the PDF print sheet -->
<script>
document.write( '<link rel="stylesheet" href="css/print/' + ( window.location.search.match( /print-pdf/gi ) ? 'pdf' : 'paper' ) + '.css" type="text/css" media="print">' );
</script>
<!--[if lt IE 9]>
<script src="lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class='reveal'>
<div class='slides'>
<section>
<h1>PureImage: raster image processing for Scala</h1>
</section>
<section>
<h2>The problem</h2>
<h3>Available image libraries are difficult to use</h3>
</section>
<section>
<h3>They feature byzantive and opaque APIs</h3>
<img src='images/bufferedimage-api.png'>
</section>
<section>
<h3>Mutable data structures</h3>
<div>(I'm preaching to the choir here)</div>
</section>
<section>
<h3>Or require concrete representations of images</h3>
<pre><code class='c'>Image *ConstituteImage(
 const size_t columns,
 const size_t rows,
 const char *map,
 const StorageType storage,
 const void *pixels,
 ExceptionInfo *exception
)</code></pre>
</section>
<section>
<h3>But what, exactly, is a raster image?</h3>
</section>
<section>
<h2>Images are simple things</h2>
<h3 class='fragment'>Let's give them a simple representation</h3>
</section>
<section>
<pre><code class='scala'>trait Image[A] {
 def width: Int
 def height: Int
 def apply(x: Int, y: Int):A
}</code></pre>
</section>
<section>
<h2>PureImage</h2>
<ul>
<li class='fragment'>Simple</li>
<li class='fragment'>(Mostly) purely functional</li>
<li class='fragment'>Fun</li>
</ul>
</section>
<section>
<h2>parsing</h2>
<pre><code class='scala'>package ps.tricerato.pureimage

sealed trait ReadError
case object UnsupportedImageType extends ReadError
case class ReadException(e: Exception) extends ReadError

sealed trait LoadedImage
case class RGBImage(image: Image[RGB]) extends LoadedImage
case class RGBAImage(image: Image[RGBA]) extends LoadedImage
case class GrayImage(image: Image[Gray]) extends LoadedImage

object Input {
 def apply(data: Array[Byte]):Either[ReadError, LoadedImage] = ???
}</code></pre>
</section>
<section>
<h2>Colors</h2>
<ul>
<li>Overall API is generic; we can represent a pixel as everything</li>
<li>For import/export, we support three types of pixels.</li>
<li>Internal represenation is as Ints:</li>
</ul>
</section>
<section>
<pre><code class='scala'>case class RGB(i: Int) extends AnyVal {
 def red = (i & 0xff)
 def green = ((i >> 8) & 0xff)
 def blue = ((i >> 16) & 0xff)
}
case class RGBA(i: Int) extends AnyVal {
 def red = (i & 0xff).toByte
 def green = ((i >> 8) & 0xff).toByte
 def blue = ((i >> 16) & 0xff).toByte
 def alpha = ((i >> 24) & 0xff).toByte
}
case class Gray(i: Int) extends AnyVal {
 def white = (i & 0xff).toByte
}
</code></pre>
</section>
<section>
<div>How can we work with pixels whose representation may be completely arbitrary in a generic way?</div>
</section>
<section>
<h2>Typeclasses</h2>
</section>
<section>
<pre><code class='scala'>trait Pixel[A] {
 def sum(a: A, b: A):A
 def fade(p: A, f: Float):A
 def zero: A
}

def f[A : Pixel](image: Image[A]) = {
 val ops = image.ops
 ...
}
</code></pre>
</section>
<section>
<h2>Output</h2>
<ul>
<li class='fragment'>Arbitrary number of pixel types</li>
<li class='fragment'>Arbitrary number of image formats</li>
<li class='fragment'>Many output formats don't support some pixel types (JPEG doesn't support an Alpha channel)</li>
<li class='fragment'>The solution?</li>
</ul>
</section>
<section>
<h2>Typeclasses</h2>
</section>
<section>
<pre><code class='scala'>trait OutputFormat
case class JPEG(quality: Int) extends OutputFormat
object PNG extends OutputFormat
object GIF extends OutputFormat

trait Output[I, O <: OutputFormat] {
 def apply(i: Image[I], o: O):Array[Byte]
}</code></pre>
</section>
<section>
<h2>Let's try something...</h2>
<div class='fragment'>First, we need a picture.</div>
</section>
<section>
<pre><code class='scala'>Input(resource("/zardoz.jpeg")) match {
 case Right(RGBImage(image)) => image
 case _ => ???
}</code></pre>
<img class='fragment' src='images/zardoz.png'>
<div class='fragment'>This will do.</div>
</section>
<section data-transition='none'>
<img src='images/fib1.png'>
<pre><code class='scala'>def fib1[A : Pixel](image: Image[A]):Image[A] = {
 import filter._
 val square = squareCrop(image)
 new Image[A] {
 def width = (square.width * PHI).toInt; def height = square.height
 def apply(x: Int, y: Int) = if (x >= square.width) {
 image.ops.zero
 } else {
 image(x,y)
 }
 }
}</code></pre>
</section>
<section data-transition='none'>
<img src='images/fib2.png'>
<pre><code class='scala'>def fib2[A : Pixel](image: Image[A]):Image[A] = {
 import filter._
 val square = squareCrop(image)
 new Image[A] {
 def width = (square.width * PHI).toInt; def height = square.height
 def apply(x: Int, y: Int) = {
 val flipped = Rotate(Rotate.Clockwise90, image)
 if (x >= square.width) {
 flipped(x - square.width, y)
 } else {
 image(x,y)
 }
 }
 }
}</code></pre>
</section>
<section data-transition='none'>
<img src='images/fib3.png'>
<pre><code class='scala'>def fib3[A : Pixel](image: Image[A]):Image[A] = {
 import filter._
 val square = squareCrop(image)
 new Image[A] {
 def width = (square.width * PHI).toInt; def height = square.height
 def apply(x: Int, y: Int) = {
 lazy val flipped = Rotate(Rotate.Clockwise90, fib3(image))
 if (x >= square.width) {
 flipped(x - square.width, y)
 } else {
 image(x,y)
 }
 }
 }
}</code></pre>
</section>
<section data-transition='none'>
<img src='images/fib4.png'>
<pre><code class='scala'>def fib4[A : Pixel](image: Image[A]):Image[A] = {
 import filter._
 val square = squareCrop(image)
 new Image[A] {
 def width = (square.width * PHI).toInt; def height = square.height
 def apply(x: Int, y: Int) = {
 lazy val flipped = Rotate(Rotate.Clockwise90, fib4(image))
 lazy val scaled = scale(
 flipped.height.toFloat / image.height,
 flipped
 )
 if (x >= square.width) {
 scaled(x - square.width, y)
 } else {
 image(x,y)
 }
 }
 }
}</code></pre>
</section>
<section data-transition='none'>
<img src='images/fib5.png'>
<pre><code class='scala'>def fib5[A : Pixel](image: Image[A]):Image[A] = {
 import filter._
 val square = squareCrop(image)
 new Image[A] {
 def width = (square.width * PHI).toInt; def height = square.height
 def apply(x: Int, y: Int) = {
 lazy val flipped = Rotate(Rotate.Clockwise90, fib4(image))
 lazy val filtered = Lanczos(PHI.toFloat * 2)(flipped)
 lazy val scaled = scale(flipped.height.toFloat / image.height, filtered)
 if (x >= square.width) {
 scaled(x - square.width, y)
 } else {
 image(x,y)
 }
 }
 }
}
</code></pre>
</section>
<section>
<h2>Included filters</h2>
<ul>
<li>Lanczos</li>
<li>Translate</li>
<li>Scale</li>
<li>Crop</li>
<li>Alpha blending</li>
<li>Whatever you want to write</li>
</ul>
</section>
<section>
<h2>Lanczos filtering</h2>
<img src='images/lanczos-graph.svg' style='background: white;'>
</section>
<section data-transition='none'>
<img src='images/unfiltered.jpeg'>
</section>
<section data-transition='none'>
<img src='images/filtered.jpeg'>
</section>
<section>
<h2>Problems</h2>
</section>
<section>
<h3>External image libraries</h3>
<ul>
<li class='fragment'>Everything is terrible</li>
<li class='fragment'>Java/AWT imaging: crazy and buggy</li>
<li class='fragment'>Apache Imaging: slow</li>
<li class='fragment'>C libraries: they are written in C</li>
</ul>
</section>
<section>
<h3>Boxing</h3>
<img src='images/boxing.jpg'>
</section>
<section>
<h4>In production, no single optimization proved as fruitful as avoiding boxing</h4>
<ul>
<li class='fragment'>Image processing involves very many small calculations</li>
<li class='fragment'>Allocation, incrementing heap pointers, and triggering (generational) GC aren't that expensive...</li>
<li class='fragment'>But they're more expensive than bitwise operations of primitives on the stack</li>
<li class='fragment'>Hence, boxing-related overhead can quickly dominate</li>
</ul>
</section>
<section>
<ul>
<li class='fragment'>Generics work well</li>
<li class='fragment'>Specialization works well</li>
<li class='fragment'>Value classes work well</li>
<li class='fragment'>They don't work well together [SI-5611]</li>
</ul>
</section>
<section>
<pre><code class='scala'>clasee WrappedInt(val i: Int) extends AnyVal

def fooRaw(i: Int) = ??? // no boxing

def fooWrapped(w: WrappedInt) = ??? // no boxing

def fooGeneric[@specialized(Int) A](i: I) = ???
fooGeneric(42) // no boxing

fooGeneric(WrappedInt(42)) // boxing!
</code></pre>
</section>
<section>
<h2>Thanks!</h2>
<ul>
<li>Stephen Judkins</li>
<li>
<a href='https://twitter.com/stephenjudkins'>@stephenjudkins</a>
</li>
<li>
<a href='https://github.com/stephenjudkins/pureimage'>https://github.com/stephenjudkins/pureimage</a>
</li>
<li>
<a href='http://tricerato.ps'>http://tricerato.ps</a>
</li>
</ul>
</section>
</div>
</div>
<script src='lib/js/head.min.js'></script>
<script src='js/reveal.min.js'></script>
<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'linear', // default/cube/page/concave/zoom/linear/fade/none
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
</script>
</body>
</html>