Skip to content

Commit 76d013e

Browse files
jstine35nstbayless
andauthored
Painter: scaling and transformation implementation (WANT_TRANSFORM=1) (#272)
- Adds image scaling and transformation features as specified by Love2D. - build with make WANT_TRANSFORM=1 (this is default behavior) - Adds unit test for scaling using grid textures, verifies draw offset applied correctly --------- Co-authored-by: nstbayless <[email protected]>
1 parent d257cfc commit 76d013e

File tree

8 files changed

+262
-46
lines changed

8 files changed

+262
-46
lines changed

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ WANT_ZLIB ?= 1
2626
WANT_UNZIP ?= 1
2727
WANT_LUASOCKET ?= 0
2828
WANT_PHYSFS ?= 0
29+
WANT_COMPOSITION ?= 1
30+
WANT_TRANSFORM ?= 1
31+
TRACE_ALLOCATION ?= 0
2932

3033
#### END CLI OPTIONS
3134

3235
# setup some things that will be reassigned per-platform
33-
HAVE_COMPOSITION ?= 1
3436
HAVE_INOTIFY ?= 0
35-
TRACE_ALLOCATION ?= 0
3637
MMD := -MMD
3738

3839
ifeq ($(LUTRO_CONFIG),)

Makefile.common

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,16 @@ ifeq ($(HAVE_INOTIFY),1)
148148
SOURCES_C += $(CORE_DIR)/live.c
149149
endif
150150

151-
ifeq ($(HAVE_COMPOSITION),1)
152-
CFLAGS += -DHAVE_COMPOSITION
151+
ifeq ($(WANT_COMPOSITION),1)
152+
CFLAGS += -DHAVE_COMPOSITION=1
153153
endif
154154

155155
ifeq ($(TRACE_ALLOCATION),1)
156-
CFLAGS += -DTRACE_ALLOCATION
156+
CFLAGS += -DTRACE_ALLOCATION=1
157+
endif
158+
159+
ifeq ($(WANT_TRANSFORM),1)
160+
CFLAGS += -DHAVE_TRANSFORM=1
157161
endif
158162

159163
# PhysFS

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ Compile Lutro by [installing the RetroArch dependencies](https://github.com/libr
3535

3636
There are a few optional defines you can use to change how Lutro behaves.
3737

38-
- `make HAVE_COMPOSITION=1` Enables alpha-blending.
39-
38+
- `make WANT_COMPOSITION=1` Enables alpha-blending.
39+
- `make WANT_TRANSFORM=1` Enables scaling
40+
- `make TRACE_ALLOCATION=1` Enables memory allocation tracing
4041

4142
## Test
4243

graphics.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -957,9 +957,17 @@ static int gfx_draw(lua_State *L)
957957
// int kx = OPTNUMBER(L, start + 8, 0);
958958
// int ky = OPTNUMBER(L, start + 9, 0);
959959

960+
#ifndef HAVE_TRANSFORM
961+
// if transformations are not enabled, we set them to default values here
962+
// in order to prevent them from having any unexpected effects whatsoever.
963+
sx = 1;
964+
sy = 1;
965+
r = 0;
966+
#endif
967+
960968
rect_t drect = {
961-
x + ox,
962-
y + oy,
969+
x - ox * sx,
970+
y - oy * sy,
963971
(int)data->width,
964972
(int)data->height
965973
};

painter.c

Lines changed: 146 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,31 @@ void pntr_clear(painter_t *p)
5757
if (!p->target->data)
5858
return;
5959

60-
uint32_t *begin = p->target->data;
61-
uint32_t *end = p->target->data + p->target->height * (p->target->pitch >> 2);
6260
uint32_t color = p->background;
6361

64-
while (begin < end)
65-
*begin++ = color;
62+
if (p->clip.x == 0 && p->clip.width == p->target->width
63+
&& p->clip.y == 0 && p->clip.height == p->target->height)
64+
{
65+
// this loop might be optimized by the compiler in comparison with
66+
// the alternative loop below.
67+
uint32_t *begin = p->target->data;
68+
uint32_t *end = p->target->data + p->target->height * (p->target->pitch >> 2);
69+
while (begin < end)
70+
{
71+
*begin++ = color;
72+
}
73+
}
74+
else
75+
{
76+
// less efficient loop than above, but can handle clipping rect.
77+
for (uint32_t x = MAX(0, p->clip.x); x < MIN(p->clip.x + p->clip.width, p->target->width); ++x)
78+
{
79+
for (uint32_t y = MAX(0, p->clip.y); y < MAX(p->clip.y + p->clip.height, y < p->target->height); ++y)
80+
{
81+
p->target->data[x + y * (p->target->pitch >> 2)] = color;
82+
}
83+
}
84+
}
6685
}
6786

6887

@@ -333,29 +352,114 @@ void pntr_draw(painter_t *p, const bitmap_t *bmp, const rect_t *src_rect, const
333352
rect_t srect = *src_rect, drect = *dst_rect;
334353

335354
drect.x += p->trans->tx;
336-
drect.y += p->trans->ty;
355+
drect.y -= p->trans->ty;
356+
357+
#ifdef HAVE_TRANSFORM
358+
// stored as 0 or 0xffffffff for masking properties.
359+
const uint32_t is_x_reversed = (p->trans->sx < 0) ? 0xffffffff : 0;
360+
const uint32_t is_y_reversed = (p->trans->sy < 0) ? 0xffffffff : 0;
361+
362+
float abs_sx = is_x_reversed ? -p->trans->sx : p->trans->sx;
363+
float abs_sy = is_y_reversed ? -p->trans->sy : p->trans->sy;
364+
365+
drect.width = srect.width * abs_sx;
366+
drect.height = srect.height * abs_sy;
367+
368+
const uint32_t k_binexp = 16;
369+
const uint32_t k_binexp_center = (1 << (k_binexp - 1));
370+
371+
// give up if scaling is small enough that division becomes untenable
372+
if (abs_sx < 1.0/k_binexp_center || abs_sy < 1.0/k_binexp_center)
373+
return;
374+
375+
const uint32_t inv_scale_x = (1 << k_binexp) / abs_sx;
376+
const uint32_t inv_scale_y = (1 << k_binexp) / abs_sy;
377+
378+
#define SCALE_DST_TO_SRC(axis, a) (((a) * inv_scale_##axis + k_binexp_center) >> k_binexp)
337379

338-
/* scaling not supported */
380+
// negative scaling reverses the top-left and bottom-right corners like so:
381+
if (is_x_reversed)
382+
{
383+
drect.x -= drect.width;
384+
}
385+
if (is_y_reversed)
386+
{
387+
drect.y -= drect.height;
388+
}
389+
#else
339390
drect.width = srect.width;
340391
drect.height = srect.height;
341392

393+
#define SCALE_DST_TO_SRC(axis, a) (a)
394+
#define is_x_reversed 0
395+
#define is_y_reversed 0
396+
#endif
397+
398+
// crop source rect to destination
342399
if (drect.x < 0)
343400
{
344-
srect.x += -drect.x;
345-
srect.width += drect.x;
401+
srect.width += SCALE_DST_TO_SRC(x, drect.x);
402+
if (!is_x_reversed)
403+
{
404+
// (crop from left side of source)
405+
srect.x += SCALE_DST_TO_SRC(x, -drect.x);
406+
}
407+
drect.width += drect.x;
408+
drect.x = 0;
346409
}
347410

348411
if (drect.y < 0)
349412
{
350-
srect.y += -drect.y;
351-
srect.height += drect.y;
413+
srect.height += SCALE_DST_TO_SRC(y, drect.y);
414+
if (!is_y_reversed)
415+
{
416+
// (crop from top of source)
417+
srect.y += SCALE_DST_TO_SRC(y, -drect.y);
418+
}
419+
drect.height += drect.y;
420+
drect.y = 0;
352421
}
353422

354-
drect = rect_intersect(&drect, &p->clip);
355-
drect.width = MIN(drect.width, srect.width);
356-
drect.height = MIN(drect.height, srect.height);
423+
rect_t drect_clipped = rect_intersect(&drect, &p->clip);
424+
425+
// (note: this can never be negative, so srect.x, srect.y remain positive)
426+
srect.x += SCALE_DST_TO_SRC(x, drect_clipped.x - drect.x);
427+
srect.width -= SCALE_DST_TO_SRC(x, drect.width - drect_clipped.width);
428+
srect.y += SCALE_DST_TO_SRC(y, drect_clipped.y - drect.y);
429+
srect.height -= SCALE_DST_TO_SRC(y, drect.height - drect_clipped.height);
357430

358-
if (rect_is_null(&drect) || rect_is_null(&srect))
431+
drect = drect_clipped;
432+
433+
// ensure source rect is cropped to source bounds
434+
if (srect.x + srect.width > bmp->width)
435+
{
436+
srect.width = bmp->width - srect.x;
437+
}
438+
if (srect.y + srect.height > bmp->height)
439+
{
440+
srect.height = bmp->height - srect.y;
441+
}
442+
443+
// ensure we won't exceed the bitmap's data during the upcoming blit:
444+
#ifdef HAVE_TRANSFORM
445+
// temporary implementation
446+
// TODO: replace this.
447+
if (srect.x >= bmp->width || srect.y >= bmp->height)
448+
return;
449+
while (srect.x + SCALE_DST_TO_SRC(x, drect.width) > bmp->width)
450+
{
451+
drect.width--;
452+
}
453+
while (srect.y + SCALE_DST_TO_SRC(y, drect.height) > bmp->height)
454+
{
455+
drect.height--;
456+
}
457+
#else
458+
drect.width = MIN(drect.width, srect.width);
459+
drect.height = MIN(drect.height, srect.height);
460+
#endif
461+
462+
if (rect_is_null(&drect) || rect_is_null(&srect) || p->trans->sx == 0 || p->trans->sy == 0)
359463
return;
360464

361465
size_t dst_skip = p->target->pitch >> 2;
@@ -368,39 +472,47 @@ void pntr_draw(painter_t *p, const bitmap_t *bmp, const rect_t *src_rect, const
368472
int cols = drect.width;
369473
int x = 0;
370474

475+
#ifdef HAVE_TRANSFORM
476+
uint32_t y = 0;
477+
#endif
371478
#ifdef HAVE_COMPOSITION
372479
uint32_t sa, sr, sg, sb, da, dr, dg, db, s, d;
480+
#else
481+
uint32_t s;
482+
#endif
373483
while (rows_left--)
374484
{
375485
for (x = 0; x < cols; ++x)
376486
{
487+
#ifdef HAVE_TRANSFORM
488+
uint32_t xo = (x & ~is_x_reversed) | ((cols - x - 1) & is_x_reversed);
489+
uint32_t yo = (y & ~is_y_reversed) | ((drect.height - y - 1) & is_y_reversed);
490+
uint32_t xi = SCALE_DST_TO_SRC(x, xo);
491+
uint32_t yi = SCALE_DST_TO_SRC(y, yo);
492+
s = src[xi + yi * src_skip];
493+
#else
377494
s = src[x];
495+
#endif
496+
#ifdef HAVE_COMPOSITION
378497
d = dst[x];
379498
sa = s >> 24;
380499
da = d >> 24;
381500
DISASSEMBLE_RGB(s, sr, sg, sb);
382501
DISASSEMBLE_RGB(d, dr, dg, db);
383502
dst[x] = ((sa + da * (255 - sa)) << 24) | (COMPOSE_FAST(sr, dr, sa) << 16) | (COMPOSE_FAST(sg, dg, sa) << 8) | (COMPOSE_FAST(sb, db, sa));
384-
}
385-
386-
dst += dst_skip;
387-
src += src_skip;
388-
}
389503
#else
390-
uint32_t c;
391-
while (rows_left--)
392-
{
393-
for (x = 0; x < cols; ++x)
394-
{
395-
c = src[x];
396-
if (c & 0xff000000)
397-
dst[x] = c;
504+
if (s & 0xff000000)
505+
dst[x] = s;
506+
#endif
398507
}
399508

400509
dst += dst_skip;
510+
#ifdef HAVE_TRANSFORM
511+
y += 1;
512+
#else
401513
src += src_skip;
402-
}
403514
#endif
515+
}
404516
}
405517

406518

@@ -453,14 +565,14 @@ void pntr_print(painter_t *p, int x, int y, const char *text, int limit)
453565
if (limit > 0 && drect.x - x > limit)
454566
{
455567
drect.x = x;
456-
drect.y += atlas->height;
457-
}
568+
drect.y += atlas->height;
569+
}
458570

459-
if (c == '\n')
460-
{
571+
if (c == '\n')
572+
{
461573
drect.x = x;
462-
drect.y += atlas->height;
463-
}
574+
drect.y += atlas->height;
575+
}
464576
}
465577

466578
if (utf32 != buf)

test/graphics/scale.lua

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
local t, rotation, scale_x, scale_y, org_x, org_y, src_x, src_w = 0, 0, 0, 0, 0, 0, 0, 0
2+
local draw_line = false
3+
local line_segments = {}
4+
local img = nil
5+
6+
return {
7+
intervalTime = 5,
8+
9+
load = function()
10+
img = lutro.graphics.newImage("graphics/font.png")
11+
line_segments[1] = {x=104, y=108} -- 100 + centred on quad
12+
end,
13+
14+
draw = function()
15+
lutro.graphics.setColor(255, 255, 255)
16+
17+
-- track scaling-offset interaction using a line
18+
if draw_line then
19+
lutro.graphics.print("Image should follow the line:", 40, 80)
20+
line_segments[#line_segments + 1] = {x=104 - scale_x * org_x, y=108 - scale_y * org_y}
21+
for idx, coord in ipairs(line_segments) do
22+
if idx > 1 then
23+
local prevcoord = line_segments[idx - 1]
24+
lutro.graphics.line(prevcoord.x, prevcoord.y, coord.x, coord.y)
25+
end
26+
end
27+
end
28+
29+
lutro.graphics.draw(img,
30+
lutro.graphics.newQuad( src_x, 0, src_w, 16, img:getWidth(), img:getHeight()),
31+
100, 100,
32+
rotation,
33+
scale_x,
34+
scale_y,
35+
org_x,
36+
org_y
37+
)
38+
end,
39+
40+
update = function(dt)
41+
-------------------------------------------
42+
-- Set parameters based on current time. --
43+
-------------------------------------------
44+
t = t + dt
45+
46+
-- rotation not supported, so we leave it as zero.
47+
rotation = 0
48+
49+
scale_x = 1 + math.sin(t * 3) * 4
50+
scale_y = math.cos(t * 2) * 2
51+
52+
-- test zero scaling (should be blank)
53+
if t < 0.2 then
54+
scale_x = 0
55+
scale_y = 0
56+
end
57+
58+
-- image offset while scaled
59+
if t > 2.5 then
60+
-- set xscale, yscale to something arbitrary
61+
-- then ensure origin is offset correctly by scale
62+
scale_x = 1.1 + math.pow(t - 2.5, 2)
63+
scale_y = 1.1 + math.pow(t - 2.5, 2)
64+
org_x = -(t - 2.5) * 20
65+
org_y = -10 - (math.sin((t - 2.5) * 3) * 10)
66+
-- (origin should follow the line being drawn)
67+
draw_line = true
68+
end
69+
70+
-- src position
71+
src_x = 9 * math.floor(t + 1)
72+
src_w = 8
73+
end
74+
}

0 commit comments

Comments
 (0)