@@ -109,108 +109,112 @@ func (img *Image) Upload() {
109
109
gl .TexSubImage2D (gl .TEXTURE_2D , 0 , 0 , 0 , img .texWidth , img .texHeight , gl .RGBA , gl .UNSIGNED_BYTE , img .Pix )
110
110
}
111
111
112
- // Draw draws the image onto the current GL framebuffer.
113
- func (img * Image ) Draw (dstBounds geom.Rectangle , srcBounds image.Rectangle ) {
112
+ // Draw draws the srcBounds part of the image onto a parallelogram, defined by
113
+ // three of its corners, in the current GL framebuffer.
114
+ func (img * Image ) Draw (topLeft , topRight , bottomLeft geom.Point , srcBounds image.Rectangle ) {
114
115
// TODO(crawshaw): Adjust viewport for the top bar on android?
115
116
gl .UseProgram (glimage .program )
116
117
117
- // We are drawing a sub-image of dst, defined by dstBounds. Let ABCD
118
- // be the image, and PQRS be the sub-image. The two images may actually
119
- // be equal, but in the general case, PQRS can be smaller:
120
- //
121
- // M
122
- // A +----+----------+ B
123
- // | |
124
- // N + P +-----+ Q |
125
- // | | | |
126
- // | S +-----+ R |
127
- // | |
128
- // D +---------------+ C
129
- //
130
- // There are two co-ordinate spaces: geom space and framebuffer space.
131
- // In geom space, the ABCD rectangle is:
132
- //
133
- // (0, 0) (geom.Width, 0)
134
- // (0, geom.Height) (geom.Width, geom.Height)
135
- //
136
- // and the PQRS rectangle is:
137
- //
138
- // (dstBounds.Min.X, dstBounds.Min.Y) (dstBounds.Max.X, dstBounds.Min.Y)
139
- // (dstBounds.Min.X, dstBounds.Max.Y) (dstBounds.Max.X, dstBounds.Max.Y)
140
- //
141
- // In framebuffer space, the ABCD rectangle is:
142
- //
143
- // (-1, +1) (+1, +1)
144
- // (-1, -1) (+1, -1)
145
- //
146
- // We need to solve for PQRS' co-ordinates in framebuffer space, and
147
- // calculate the MVP matrix that transforms the -1/+1 ABCD co-ordinates
148
- // to PQRS co-ordinates.
149
- //
150
- // To solve for PQRS, note that PQ / AB must match in both spaces. Call
151
- // this ratio fracX, and likewise for fracY.
152
- //
153
- // [EQ1] fracX = (dstBounds.Max.X - dstBounds.Min.X) / geom.Width
154
- //
155
- // Similarly, the AM / AB ratio must match:
156
- //
157
- // (P.x - -1) / (1 - -1) = dstBounds.Min.X / geom.Width
158
- //
159
- // where the LHS is in framebuffer space and the RHS in geom space. This
160
- // equation is equivalent to:
161
- //
162
- // [EQ2] P.x = -1 + 2 * dstBounds.Min.X / geom.Width
163
- //
164
- // This MVP matrix is a scale followed by a translate. The scale is by
165
- // (fracX, fracY). After this, our corners have been transformed to:
166
- //
167
- // (-fracX, +fracY) (+fracX, +fracY)
168
- // (-fracX, -fracY) (+fracX, -fracY)
169
- //
170
- // so the translate is by (P.x + fracX) in the X direction, and
171
- // likewise for Y. Combining equations EQ1 and EQ2 simplifies the
172
- // translate to be:
173
- //
174
- // -1 + (dstBounds.Max.X + dstBounds.Min.X) / geom.Width
175
- // +1 - (dstBounds.Max.Y + dstBounds.Min.Y) / geom.Height
176
- var a f32.Affine
177
- a .Identity ()
178
- a .Translate (
179
- & a ,
180
- - 1 + float32 ((dstBounds .Max .X + dstBounds .Min .X )/ geom .Width ),
181
- + 1 - float32 ((dstBounds .Max .Y + dstBounds .Min .Y )/ geom .Height ),
182
- )
183
- a .Scale (
184
- & a ,
185
- float32 ((dstBounds .Max .X - dstBounds .Min .X )/ geom .Width ),
186
- float32 ((dstBounds .Max .Y - dstBounds .Min .Y )/ geom .Height ),
187
- )
188
- glimage .mvp .WriteAffine (& a )
189
-
190
- // Texture UV co-ordinates start out as:
191
- //
192
- // (0,0) (1,0)
193
- // (0,1) (1,1)
194
- //
195
- // These co-ordinates need to be scaled to texWidth/Height,
196
- // which may be less than 1 as the source image may not have
197
- // power-of-2 dimensions. Then it is scaled and translated
198
- // to represent the srcBounds rectangle of the source texture.
199
- //
200
- // The math is simpler here because in both co-ordinate spaces,
201
- // the top-left corner is (0, 0).
202
- a .Identity ()
203
- a .Translate (
204
- & a ,
205
- float32 (srcBounds .Min .X )/ float32 (img .texWidth ),
206
- float32 (srcBounds .Min .Y )/ float32 (img .texHeight ),
207
- )
208
- a .Scale (
209
- & a ,
210
- float32 (srcBounds .Dx ())/ float32 (img .texWidth ),
211
- float32 (srcBounds .Dy ())/ float32 (img .texHeight ),
212
- )
213
- glimage .uvp .WriteAffine (& a )
118
+ {
119
+ // We are drawing a parallelogram PQRS, defined by three of its
120
+ // corners, onto the entire GL framebuffer ABCD. The two quads may
121
+ // actually be equal, but in the general case, PQRS can be smaller,
122
+ // and PQRS is not necessarily axis-aligned.
123
+ //
124
+ // A +---------------+ B
125
+ // | P +-----+ Q |
126
+ // | | | |
127
+ // | S +-----+ R |
128
+ // D +---------------+ C
129
+ //
130
+ // There are two co-ordinate spaces: geom space and framebuffer space.
131
+ // In geom space, the ABCD rectangle is:
132
+ //
133
+ // (0, 0) (geom.Width, 0)
134
+ // (0, geom.Height) (geom.Width, geom.Height)
135
+ //
136
+ // and the PQRS quad is:
137
+ //
138
+ // (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
139
+ // (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
140
+ //
141
+ // In framebuffer space, the ABCD rectangle is:
142
+ //
143
+ // (-1, +1) (+1, +1)
144
+ // (-1, -1) (+1, -1)
145
+ //
146
+ // First of all, convert from geom space to framebuffer space. For
147
+ // later convenience, we divide everything by 2 here: px2 is half of
148
+ // the P.X co-ordinate (in framebuffer space).
149
+ px2 := - 0.5 + float32 (topLeft .X / geom .Width )
150
+ py2 := + 0.5 - float32 (topLeft .Y / geom .Height )
151
+ qx2 := - 0.5 + float32 (topRight .X / geom .Width )
152
+ qy2 := + 0.5 - float32 (topRight .Y / geom .Height )
153
+ sx2 := - 0.5 + float32 (bottomLeft .X / geom .Width )
154
+ sy2 := + 0.5 - float32 (bottomLeft .Y / geom .Height )
155
+ // Next, solve for the affine transformation matrix
156
+ // [ a00 a01 a02 ]
157
+ // a = [ a10 a11 a12 ]
158
+ // [ 0 0 1 ]
159
+ // that maps A to P:
160
+ // a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
161
+ // and likewise maps B to Q and D to S. Solving those three constraints
162
+ // implies that C maps to R, since affine transformations keep parallel
163
+ // lines parallel. This gives 6 equations in 6 unknowns:
164
+ // -a00 + a01 + a02 = 2*px2
165
+ // -a10 + a11 + a12 = 2*py2
166
+ // +a00 + a01 + a02 = 2*qx2
167
+ // +a10 + a11 + a12 = 2*qy2
168
+ // -a00 - a01 + a02 = 2*sx2
169
+ // -a10 - a11 + a12 = 2*sy2
170
+ // which gives:
171
+ // a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
172
+ // and similarly for the other elements of a.
173
+ glimage .mvp .WriteAffine (& f32.Affine {{
174
+ qx2 - px2 ,
175
+ px2 - sx2 ,
176
+ qx2 + sx2 ,
177
+ }, {
178
+ qy2 - py2 ,
179
+ py2 - sy2 ,
180
+ qy2 + sy2 ,
181
+ }})
182
+ }
183
+
184
+ {
185
+ // Mapping texture co-ordinates is similar, except that in texture
186
+ // space, the ABCD rectangle is:
187
+ //
188
+ // (0,0) (1,0)
189
+ // (0,1) (1,1)
190
+ //
191
+ // and the PQRS quad is always axis-aligned. First of all, convert
192
+ // from pixel space to texture space.
193
+ w := float32 (img .texWidth )
194
+ h := float32 (img .texHeight )
195
+ px := float32 (srcBounds .Min .X - img .Rect .Min .X ) / w
196
+ py := float32 (srcBounds .Min .Y - img .Rect .Min .Y ) / h
197
+ qx := float32 (srcBounds .Max .X - img .Rect .Min .X ) / w
198
+ sy := float32 (srcBounds .Max .Y - img .Rect .Min .Y ) / h
199
+ // Due to axis alignment, qy = py and sx = px.
200
+ //
201
+ // The simultaneous equations are:
202
+ // 0 + 0 + a02 = px
203
+ // 0 + 0 + a12 = py
204
+ // a00 + 0 + a02 = qx
205
+ // a10 + 0 + a12 = qy = py
206
+ // 0 + a01 + a02 = sx = px
207
+ // 0 + a11 + a12 = sy
208
+ glimage .uvp .WriteAffine (& f32.Affine {{
209
+ qx - px ,
210
+ 0 ,
211
+ px ,
212
+ }, {
213
+ 0 ,
214
+ sy - py ,
215
+ py ,
216
+ }})
217
+ }
214
218
215
219
gl .ActiveTexture (gl .TEXTURE0 )
216
220
gl .BindTexture (gl .TEXTURE_2D , img .Texture )
@@ -231,15 +235,38 @@ func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
231
235
}
232
236
233
237
var quadXYCoords = f32 .Bytes (binary .LittleEndian ,
234
- - 1 , - 1 , // bottom left
235
- + 1 , - 1 , // bottom right
236
238
- 1 , + 1 , // top left
237
239
+ 1 , + 1 , // top right
240
+ - 1 , - 1 , // bottom left
241
+ + 1 , - 1 , // bottom right
238
242
)
239
243
240
244
var quadUVCoords = f32 .Bytes (binary .LittleEndian ,
241
- 0 , 1 , // bottom left
242
- 1 , 1 , // bottom right
243
245
0 , 0 , // top left
244
246
1 , 0 , // top right
247
+ 0 , 1 , // bottom left
248
+ 1 , 1 , // bottom right
245
249
)
250
+
251
+ const vertexShader = `#version 100
252
+ uniform mat3 mvp;
253
+ uniform mat3 uvp;
254
+ attribute vec3 pos;
255
+ attribute vec2 inUV;
256
+ varying vec2 UV;
257
+ void main() {
258
+ vec3 p = pos;
259
+ p.z = 1.0;
260
+ gl_Position = vec4(mvp * p, 1);
261
+ UV = (uvp * vec3(inUV, 1)).xy;
262
+ }
263
+ `
264
+
265
+ const fragmentShader = `#version 100
266
+ precision mediump float;
267
+ varying vec2 UV;
268
+ uniform sampler2D textureSample;
269
+ void main(){
270
+ gl_FragColor = texture2D(textureSample, UV);
271
+ }
272
+ `
0 commit comments